[CODE-1144] UI : Added author filter on PR page (#855)

This commit is contained in:
Ritik Kapoor 2023-12-12 18:26:25 +00:00 committed by Harness
parent 0a4d370676
commit d3fe654e23
3 changed files with 142 additions and 7 deletions

View File

@ -56,19 +56,18 @@ export default function PullRequests() {
UserPreference.PULL_REQUESTS_FILTER_SELECTED_OPTIONS, UserPreference.PULL_REQUESTS_FILTER_SELECTED_OPTIONS,
PullRequestFilterOption.OPEN PullRequestFilterOption.OPEN
) )
const [authorFilter, setAuthorFilter] = useState<string>()
const space = useGetSpaceParam() const space = useGetSpaceParam()
const { updateQueryParams } = useUpdateQueryParams() const { updateQueryParams } = useUpdateQueryParams()
const pageBrowser = useQueryParams<PageBrowserProps>() const pageBrowser = useQueryParams<PageBrowserProps>()
const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1 const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1
const [page, setPage] = usePageIndex(pageInit) const [page, setPage] = usePageIndex(pageInit)
useEffect(() => { useEffect(() => {
if (page > 1) { if (page > 1) {
updateQueryParams({ page: page.toString() }) updateQueryParams({ page: page.toString() })
} }
}, [setPage]) // eslint-disable-line react-hooks/exhaustive-deps }, [setPage]) // eslint-disable-line react-hooks/exhaustive-deps
const { repoMetadata, error, loading, refetch } = useGetRepositoryMetadata() const { repoMetadata, error, loading, refetch } = useGetRepositoryMetadata()
const { const {
data, data,
@ -84,7 +83,8 @@ export default function PullRequests() {
sort: filter == PullRequestFilterOption.MERGED ? 'merged' : 'number', sort: filter == PullRequestFilterOption.MERGED ? 'merged' : 'number',
order: 'desc', order: 'desc',
query: searchTerm, query: searchTerm,
state: filter == PullRequestFilterOption.ALL ? '' : filter state: filter == PullRequestFilterOption.ALL ? '' : filter,
...(authorFilter && { created_by: Number(authorFilter) })
}, },
debounce: 500, debounce: 500,
lazy: !repoMetadata lazy: !repoMetadata
@ -234,6 +234,11 @@ export default function PullRequests() {
setSearchTerm(value) setSearchTerm(value)
setPage(1) setPage(1)
}} }}
activePullRequestAuthorFilterOption={authorFilter}
onPullRequestAuthorFilterChanged={_authorFilter => {
setAuthorFilter(_authorFilter)
setPage(1)
}}
/> />
<Container padding="xlarge"> <Container padding="xlarge">
<Match expr={data?.length}> <Match expr={data?.length}>

View File

@ -16,11 +16,14 @@
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, Button } from '@harnessio/uicore' import { Container, Layout, FlexExpander, DropDown, ButtonVariation, Button, SelectOption } from '@harnessio/uicore'
import { sortBy } from 'lodash-es'
import { getConfig, getUsingFetch } from 'services/config'
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 { UserPreference, useUserPreference } from 'hooks/useUserPreference' import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import type { TypesPrincipalInfo } from 'services/code'
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner' import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
import { permissionProps } from 'utils/Utils' import { permissionProps } from 'utils/Utils'
@ -29,15 +32,19 @@ import css from './PullRequestsContentHeader.module.scss'
interface PullRequestsContentHeaderProps extends Pick<GitInfoProps, 'repoMetadata'> { interface PullRequestsContentHeaderProps extends Pick<GitInfoProps, 'repoMetadata'> {
loading?: boolean loading?: boolean
activePullRequestFilterOption?: string activePullRequestFilterOption?: string
activePullRequestAuthorFilterOption?: string
onPullRequestFilterChanged: (filter: string) => void onPullRequestFilterChanged: (filter: string) => void
onPullRequestAuthorFilterChanged: (authorFilter: string) => void
onSearchTermChanged: (searchTerm: string) => void onSearchTermChanged: (searchTerm: string) => void
} }
export function PullRequestsContentHeader({ export function PullRequestsContentHeader({
loading, loading,
onPullRequestFilterChanged, onPullRequestFilterChanged,
onPullRequestAuthorFilterChanged,
onSearchTermChanged, onSearchTermChanged,
activePullRequestFilterOption = PullRequestFilterOption.OPEN, activePullRequestFilterOption = PullRequestFilterOption.OPEN,
activePullRequestAuthorFilterOption,
repoMetadata repoMetadata
}: PullRequestsContentHeaderProps) { }: PullRequestsContentHeaderProps) {
const history = useHistory() const history = useHistory()
@ -47,10 +54,13 @@ export function PullRequestsContentHeader({
UserPreference.PULL_REQUESTS_FILTER_SELECTED_OPTIONS, UserPreference.PULL_REQUESTS_FILTER_SELECTED_OPTIONS,
activePullRequestFilterOption activePullRequestFilterOption
) )
const [searchTerm, setSearchTerm] = useState('')
const space = useGetSpaceParam()
const { standalone } = useAppContext() const [authorFilterOption, setAuthorFilterOption] = useState(activePullRequestAuthorFilterOption)
const [searchTerm, setSearchTerm] = useState('')
const [query, setQuery] = useState<string>('')
const [loadingAuthors, setLoadingAuthors] = useState<boolean>(false)
const space = useGetSpaceParam()
const { standalone, routingId } = useAppContext()
const { hooks } = useAppContext() const { hooks } = useAppContext()
const permPushResult = hooks?.usePermissionTranslate?.( const permPushResult = hooks?.usePermissionTranslate?.(
{ {
@ -73,6 +83,40 @@ export function PullRequestsContentHeader({
[getString] [getString]
) )
const getAuthorsPromise = (): Promise<SelectOption[]> => {
return new Promise((resolve, reject) => {
setLoadingAuthors(true)
try {
getUsingFetch(getConfig('code/api/v1'), `/principals`, {
queryParams: {
query: query?.trim(),
type: 'user',
accountIdentifier: routingId
}
})
.then((obj: TypesPrincipalInfo[]) => {
const updatedAuthorsList = Array.isArray(obj)
? ([
...(obj || []).map(item => ({
label: String(item?.display_name),
value: String(item?.id)
}))
] as SelectOption[])
: ([] as SelectOption[])
setLoadingAuthors(false)
resolve(sortBy(updatedAuthorsList, item => item.label.toLowerCase()))
})
.catch(error => {
setLoadingAuthors(false)
reject(error)
})
} catch (error) {
setLoadingAuthors(false)
reject(error)
}
})
}
return ( return (
<Container className={css.main} padding="xlarge"> <Container className={css.main} padding="xlarge">
<Layout.Horizontal spacing="medium"> <Layout.Horizontal spacing="medium">
@ -86,6 +130,27 @@ export function PullRequestsContentHeader({
}} }}
/> />
<FlexExpander /> <FlexExpander />
<DropDown
value={authorFilterOption}
items={() => getAuthorsPromise()}
disabled={loadingAuthors}
onChange={({ value, label }) => {
setAuthorFilterOption(label as string)
onPullRequestAuthorFilterChanged(value as string)
}}
popoverClassName={css.branchDropdown}
icon="nav-user-profile"
iconProps={{ size: 16 }}
placeholder="Select Authors"
addClearBtn={true}
resetOnClose
resetOnSelect
resetOnQuery
query={query}
onQueryChange={newQuery => {
setQuery(newQuery)
}}
/>
<DropDown <DropDown
value={filterOption} value={filterOption}
items={items} items={items}

View File

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import qs, { IStringifyOptions } from 'qs'
export const getConfig = (str: string): string => { export const getConfig = (str: string): string => {
// 'code/api/v1' -> 'api/v1' (standalone) // 'code/api/v1' -> 'api/v1' (standalone)
// -> 'code/api/v1' (embedded inside Harness platform) // -> 'code/api/v1' (embedded inside Harness platform)
@ -23,3 +25,66 @@ export const getConfig = (str: string): string => {
return window.apiUrl ? `${window.apiUrl}/${str}` : `${window.harnessNameSpace || ''}/${str}` return window.apiUrl ? `${window.apiUrl}/${str}` : `${window.harnessNameSpace || ''}/${str}`
} }
export interface GetUsingFetchProps<
_TData = any,
_TError = any,
TQueryParams = {
[key: string]: any
},
TPathParams = {
[key: string]: any
}
> {
queryParams?: TQueryParams
queryParamStringifyOptions?: IStringifyOptions
pathParams?: TPathParams
requestOptions?: RequestInit
mock?: _TData
}
export const getUsingFetch = <
TData = any,
_TError = any,
TQueryParams = {
[key: string]: any
},
TPathParams = {
[key: string]: any
}
>(
base: string,
path: string,
props: GetUsingFetchProps<TData, _TError, TQueryParams, TPathParams>,
signal?: RequestInit['signal']
): Promise<TData> => {
if (props.mock) return Promise.resolve(props.mock)
let url = base + path
if (props.queryParams && Object.keys(props.queryParams).length) {
url += `?${qs.stringify(props.queryParams, props.queryParamStringifyOptions)}`
}
return fetch(url, {
signal,
...(props.requestOptions || {})
// headers: getHeaders(props.requestOptions?.headers)
}).then(res => {
// custom event to allow the app framework to handle api responses
const responseEvent = new CustomEvent('PROMISE_API_RESPONSE', { detail: { response: res } })
window.dispatchEvent(responseEvent) // this will be captured in App.tsx to handle 401 and token refresh
const contentType = res.headers.get('content-type') || ''
if (contentType.toLowerCase().indexOf('application/json') > -1) {
if (res.status === 401) {
return res.json().then(json => Promise.reject(json))
}
return res.json()
}
if (res.status === 401) {
return res.text().then(text => Promise.reject(text))
}
return res.text()
})
}