mirror of https://github.com/harness/drone.git
feat: [CODE-3214] added support for Pull Requests on Account/Org/Project in Harness code (#3521)
* fix: [CODE-3194] addressed comments * fix: [CODE-3194] addressed comments * fix: [CODE-3194] extract PR Author Filter to a common component * fix: [CODE-3194] comments * fix: [CODE-3194] comments * fix: [CODE-3194] lint * fix: [CODE-3214] lint check * feat: [CODE-3214] added support for Account/Org/Project in Harness codetry-new-ui
parent
75ec960ef2
commit
efa98ba52e
|
@ -52,6 +52,7 @@ module.exports = {
|
|||
'./Webhooks': './src/pages/Webhooks/Webhooks.tsx',
|
||||
'./WebhookNew': './src/pages/WebhookNew/WebhookNew.tsx',
|
||||
'./Search': './src/pages/Search/CodeSearchPage.tsx',
|
||||
'./SpacePullRequests': '/src/pages/ManageSpace/SpacePullRequests/SpacePullRequests.tsx',
|
||||
'./Labels': './src/pages/ManageSpace/ManageRepositories/ManageRepositories.tsx',
|
||||
'./WebhookDetails': './src/pages/WebhookDetails/WebhookDetails.tsx',
|
||||
'./NewRepoModalButton': './src/components/NewRepoModalButton/NewRepoModalButton.tsx',
|
||||
|
|
|
@ -39,7 +39,8 @@ export const defaultCurrentUser: Required<TypesUser> = {
|
|||
updated: 0,
|
||||
display_name: '',
|
||||
email: '',
|
||||
uid: ''
|
||||
uid: '',
|
||||
id: 0
|
||||
}
|
||||
|
||||
const AppContext = React.createContext<AppContextProps>({
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
LIST_FETCHING_LIMIT,
|
||||
LabelFilterObj,
|
||||
LabelFilterType,
|
||||
ScopeLevel,
|
||||
getErrorMessage,
|
||||
getScopeData
|
||||
} from 'utils/Utils'
|
||||
|
@ -53,8 +54,9 @@ interface LabelFilterProps {
|
|||
setLabelFilterOption: React.Dispatch<React.SetStateAction<LabelFilterObj[] | undefined>>
|
||||
onPullRequestLabelFilterChanged: (labelFilter: LabelFilterObj[]) => void
|
||||
bearerToken: string
|
||||
repoMetadata: RepoRepositoryOutput
|
||||
spaceRef: string
|
||||
filterScope: ScopeLevel
|
||||
repoMetadata?: RepoRepositoryOutput
|
||||
}
|
||||
|
||||
enum utilFilterType {
|
||||
|
@ -76,6 +78,7 @@ export const LabelFilter = (props: LabelFilterProps) => {
|
|||
onPullRequestLabelFilterChanged,
|
||||
bearerToken,
|
||||
repoMetadata,
|
||||
filterScope,
|
||||
spaceRef
|
||||
} = props
|
||||
const { showError } = useToaster()
|
||||
|
@ -90,22 +93,36 @@ export const LabelFilter = (props: LabelFilterProps) => {
|
|||
const [labelItems, setLabelItems] = useState<SelectOption[]>()
|
||||
const { getString } = useStrings()
|
||||
|
||||
const [accountIdentifier, orgIdentifier, projectIdentifier] = spaceRef?.split('/') || []
|
||||
|
||||
const getLabelsOnRepoScope = () =>
|
||||
getUsingFetch(getConfig('code/api/v1'), `/repos/${repoMetadata?.path}/+/labels`, bearerToken, {
|
||||
queryParams: {
|
||||
page: 1,
|
||||
limit: LIST_FETCHING_LIMIT,
|
||||
inherited: true,
|
||||
query: labelQuery?.trim(),
|
||||
accountIdentifier: routingId
|
||||
}
|
||||
})
|
||||
|
||||
const getLabelsOnSpaceScope = () =>
|
||||
getUsingFetch(getConfig('code/api/v1'), `/labels`, bearerToken, {
|
||||
queryParams: {
|
||||
accountIdentifier: accountIdentifier ?? routingId,
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
page: 1,
|
||||
limit: LIST_FETCHING_LIMIT,
|
||||
inherited: true,
|
||||
query: labelQuery?.trim()
|
||||
}
|
||||
})
|
||||
|
||||
const getDropdownLabels = async (currentFilterOption?: LabelFilterObj[]) => {
|
||||
try {
|
||||
const fetchedLabels: TypesLabel[] = await getUsingFetch(
|
||||
getConfig('code/api/v1'),
|
||||
`/repos/${repoMetadata?.path}/+/labels`,
|
||||
bearerToken,
|
||||
{
|
||||
queryParams: {
|
||||
page: 1,
|
||||
limit: LIST_FETCHING_LIMIT,
|
||||
inherited: true,
|
||||
query: labelQuery?.trim(),
|
||||
accountIdentifier: routingId
|
||||
}
|
||||
}
|
||||
)
|
||||
const fetchedLabels: TypesLabel[] =
|
||||
filterScope === ScopeLevel.SPACE ? await getLabelsOnSpaceScope() : await getLabelsOnRepoScope()
|
||||
const updateLabelsList = mapToSelectOptions(fetchedLabels)
|
||||
const labelForTop = mapToSelectOptions(currentFilterOption?.map(({ labelObj }) => labelObj))
|
||||
const mergedArray = [...labelForTop, ...updateLabelsList]
|
||||
|
|
|
@ -23,7 +23,7 @@ import { useStrings } from 'framework/strings'
|
|||
import type { TypesPullReq } from 'services/code'
|
||||
import css from './PullRequestStateLabel.module.scss'
|
||||
|
||||
export const PullRequestStateLabel: React.FC<{ data: TypesPullReq; iconSize?: number; iconOnly?: boolean }> = ({
|
||||
export const PullRequestStateLabel: React.FC<{ data?: TypesPullReq; iconSize?: number; iconOnly?: boolean }> = ({
|
||||
data,
|
||||
iconSize = 20,
|
||||
iconOnly = false
|
||||
|
@ -51,7 +51,7 @@ export const PullRequestStateLabel: React.FC<{ data: TypesPullReq; iconSize?: nu
|
|||
css: css.open
|
||||
}
|
||||
}
|
||||
const map = data.is_draft ? maps.draft : maps[data.state || 'unknown']
|
||||
const map = data?.is_draft ? maps.draft : maps[data?.state || 'unknown']
|
||||
|
||||
return (
|
||||
<Text
|
||||
|
@ -60,7 +60,7 @@ export const PullRequestStateLabel: React.FC<{ data: TypesPullReq; iconSize?: nu
|
|||
icon={map.icon as IconName}
|
||||
iconProps={{ size: iconOnly ? iconSize : 12 }}>
|
||||
{!iconOnly && (
|
||||
<StringSubstitute str={getString('pr.state')} vars={{ state: data.is_draft ? 'draft' : data.state }} />
|
||||
<StringSubstitute str={getString('pr.state')} vars={{ state: data?.is_draft ? 'draft' : data?.state }} />
|
||||
)}
|
||||
</Text>
|
||||
)
|
||||
|
|
|
@ -106,7 +106,7 @@ interface PrevNextPaginationProps {
|
|||
skipLayout?: boolean
|
||||
}
|
||||
|
||||
function PrevNextPagination({ onPrev, onNext, skipLayout }: PrevNextPaginationProps) {
|
||||
export function PrevNextPagination({ onPrev, onNext, skipLayout }: PrevNextPaginationProps) {
|
||||
const { getString } = useStrings()
|
||||
|
||||
return (
|
||||
|
|
|
@ -441,6 +441,7 @@ export interface StringsMap {
|
|||
fileTooLarge: string
|
||||
files: string
|
||||
filesChanged: string
|
||||
filterByAuthor: string
|
||||
findATag: string
|
||||
findAUser: string
|
||||
findBranch: string
|
||||
|
@ -522,6 +523,7 @@ export interface StringsMap {
|
|||
'importSpace.title': string
|
||||
in: string
|
||||
inactiveBranches: string
|
||||
includeSubspacePR: string
|
||||
invalidResponse: string
|
||||
isRequired: string
|
||||
italic: string
|
||||
|
@ -779,6 +781,7 @@ export interface StringsMap {
|
|||
'pr.branchHasNoConflicts': string
|
||||
'pr.cantBeMerged': string
|
||||
'pr.cantMerge': string
|
||||
'pr.changesRequested': string
|
||||
'pr.checkingToMerge': string
|
||||
'pr.checks': string
|
||||
'pr.checksFailure': string
|
||||
|
@ -826,6 +829,7 @@ export interface StringsMap {
|
|||
'pr.metaLine': string
|
||||
'pr.modalTitle': string
|
||||
'pr.moreComments': string
|
||||
'pr.myPRs': string
|
||||
'pr.openForReview': string
|
||||
'pr.outdated': string
|
||||
'pr.prBranchDeleteInfo': string
|
||||
|
@ -847,6 +851,7 @@ export interface StringsMap {
|
|||
'pr.requestSubmitted': string
|
||||
'pr.requestedChanges': string
|
||||
'pr.reviewChanges': string
|
||||
'pr.reviewRequested': string
|
||||
'pr.reviewSubmitted': string
|
||||
'pr.showDiff': string
|
||||
'pr.showLink': string
|
||||
|
@ -876,6 +881,7 @@ export interface StringsMap {
|
|||
prHasNoConflicts: string
|
||||
prMustSelectSourceAndTargetBranches: string
|
||||
'prReview.assigned': string
|
||||
'prReview.filterByReviews': string
|
||||
'prReview.removed': string
|
||||
'prReview.requested': string
|
||||
'prReview.selfAssigned': string
|
||||
|
@ -1023,7 +1029,6 @@ export interface StringsMap {
|
|||
'securitySettings.vulnerabilityScanning': string
|
||||
'securitySettings.vulnerabilityScanningDesc': string
|
||||
seeNMoreMatches: string
|
||||
selectAuthor: string
|
||||
selectBranchPlaceHolder: string
|
||||
selectLanguagePlaceholder: string
|
||||
selectMergeStrat: string
|
||||
|
|
|
@ -364,6 +364,9 @@ pr:
|
|||
checks: Checks
|
||||
checksFailure: '{failedCount}/{total} checks failed'
|
||||
addDescription: Add Description
|
||||
reviewRequested: Review Requested
|
||||
changesRequested: Changes Requested
|
||||
myPRs: My Pull Requests
|
||||
prState:
|
||||
draftHeading: This pull request is still a work in progress
|
||||
draftDesc: Draft pull requests cannot be merged.
|
||||
|
@ -383,6 +386,7 @@ prReview:
|
|||
selfAssigned: '{reviewer} self-requested a review'
|
||||
removed: '{author} removed the request for review from {reviewer}'
|
||||
selfRemoved: '{author} removed their request for review'
|
||||
filterByReviews: Filter by Reviews
|
||||
webhookListingContent: 'create,delete,deployment ...'
|
||||
general: 'General'
|
||||
webhooks: 'Webhooks'
|
||||
|
@ -551,7 +555,8 @@ zoomIn: Zoom In
|
|||
zoomOut: Zoom Out
|
||||
checks: Checks
|
||||
blameCommitLine: '{author} committed {timestamp}'
|
||||
selectAuthor: Select Author
|
||||
filterByAuthor: Filter by Author
|
||||
includeSubspacePR: Include sub-space PRs
|
||||
tooltipRepoEdit: You are not authorized to {PERMS}
|
||||
missingPerms: 'You are missing the following permission:'
|
||||
createRepoPerms: 'Create / Edit Repository'
|
||||
|
|
|
@ -26,7 +26,7 @@ import LabelsListing from 'pages/Labels/LabelsListing'
|
|||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||
import type { CODEProps } from 'RouteDefinitions'
|
||||
import { useGetCurrentPageScope } from 'hooks/useGetCurrentPageScope'
|
||||
import css from './ManageRepositories.module.scss'
|
||||
import css from '../ManageSpace.module.scss'
|
||||
|
||||
export default function ManageRepositories() {
|
||||
const { settingSection } = useParams<CODEProps>()
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.main {
|
||||
min-height: var(--page-height);
|
||||
background-color: var(--primary-bg) !important;
|
||||
|
||||
.table {
|
||||
.row {
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
|
||||
.convoIcon {
|
||||
padding-top: 1px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.convo {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.prLabels {
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.prwrap {
|
||||
width: 80% !important;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.rowLink:hover {
|
||||
&,
|
||||
*:not(a) {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.titleRow {
|
||||
padding-left: var(--spacing-small);
|
||||
align-items: center;
|
||||
padding: var(--spacing-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.state {
|
||||
font-weight: 600;
|
||||
}
|
29
web/src/pages/ManageSpace/SpacePullRequests/SpacePullRequests.module.scss.d.ts
vendored
Normal file
29
web/src/pages/ManageSpace/SpacePullRequests/SpacePullRequests.module.scss.d.ts
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const convo: string
|
||||
export declare const convoIcon: string
|
||||
export declare const main: string
|
||||
export declare const prLabels: string
|
||||
export declare const prwrap: string
|
||||
export declare const row: string
|
||||
export declare const rowLink: string
|
||||
export declare const state: string
|
||||
export declare const table: string
|
||||
export declare const title: string
|
||||
export declare const titleRow: string
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import cx from 'classnames'
|
||||
import { Container, Tabs, Page } from '@harnessio/uicore'
|
||||
import { omit } from 'lodash-es'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { PullRequestFilterOption, PullRequestReviewFilterOption, SpacePRTabs } from 'utils/GitUtils'
|
||||
import { PageBrowserProps, ScopeLevelEnum } from 'utils/Utils'
|
||||
import { useQueryParams } from 'hooks/useQueryParams'
|
||||
import { useUpdateQueryParams } from 'hooks/useUpdateQueryParams'
|
||||
import { SpacePullRequestsListing } from 'pages/PullRequests/SpacePullRequestsListing'
|
||||
import css from '../ManageSpace.module.scss'
|
||||
|
||||
export default function SpacePullRequests() {
|
||||
const browserParams = useQueryParams<PageBrowserProps>()
|
||||
const { updateQueryParams, replaceQueryParams } = useUpdateQueryParams()
|
||||
const [activeTab, setActiveTab] = useState<string>(browserParams.tab || SpacePRTabs.CREATED)
|
||||
const [includeSubspaces, setIncludeSubspaces] = useState<ScopeLevelEnum>(
|
||||
browserParams?.subspace || ScopeLevelEnum.CURRENT
|
||||
)
|
||||
const { getString } = useStrings()
|
||||
|
||||
useEffect(() => {
|
||||
const params = {
|
||||
...browserParams,
|
||||
tab: browserParams.tab ?? SpacePRTabs.CREATED,
|
||||
...(!browserParams.state && { state: PullRequestFilterOption.OPEN })
|
||||
}
|
||||
updateQueryParams(params, undefined, true)
|
||||
}, [browserParams])
|
||||
|
||||
const tabListArray = [
|
||||
{
|
||||
id: SpacePRTabs.CREATED,
|
||||
title: getString('pr.myPRs'),
|
||||
panel: (
|
||||
<SpacePullRequestsListing
|
||||
activeTab={SpacePRTabs.CREATED}
|
||||
includeSubspaces={includeSubspaces}
|
||||
setIncludeSubspaces={setIncludeSubspaces}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: SpacePRTabs.REVIEW_REQUESTED,
|
||||
title: getString('pr.reviewRequested'),
|
||||
panel: (
|
||||
<SpacePullRequestsListing
|
||||
activeTab={SpacePRTabs.REVIEW_REQUESTED}
|
||||
includeSubspaces={includeSubspaces}
|
||||
setIncludeSubspaces={setIncludeSubspaces}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
return (
|
||||
<Container className={css.main}>
|
||||
<Page.Header title={getString('pullRequests')} />
|
||||
|
||||
<Container className={cx(css.main, css.tabsContainer)}>
|
||||
<Tabs
|
||||
id="SettingsTabs"
|
||||
large={false}
|
||||
defaultSelectedTabId={activeTab}
|
||||
animate={false}
|
||||
onChange={(id: string) => {
|
||||
setActiveTab(id)
|
||||
const params = {
|
||||
...browserParams,
|
||||
tab: id
|
||||
}
|
||||
if (id === SpacePRTabs.CREATED) {
|
||||
replaceQueryParams(omit(params, 'review'), undefined, true)
|
||||
} else {
|
||||
const updatedParams = { ...params, review: PullRequestReviewFilterOption.PENDING }
|
||||
replaceQueryParams(updatedParams, undefined, true)
|
||||
}
|
||||
}}
|
||||
tabList={tabListArray}></Tabs>
|
||||
</Container>
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { Layout, DropDown, SelectOption, Text } from '@harnessio/uicore'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import { sortBy } from 'lodash-es'
|
||||
import { getConfig, getUsingFetch } from 'services/config'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import type { TypesPrincipalInfo, TypesUser } from 'services/code'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import css from './PullRequestsContentHeader.module.scss'
|
||||
|
||||
interface PullRequestsContentHeaderProps {
|
||||
bearerToken: string
|
||||
activePullRequestAuthorFilterOption?: string
|
||||
activePullRequestAuthorObj?: TypesPrincipalInfo | null
|
||||
onPullRequestAuthorFilterChanged: (authorFilter: string) => void
|
||||
}
|
||||
|
||||
export function PRAuthorFilter({
|
||||
onPullRequestAuthorFilterChanged,
|
||||
activePullRequestAuthorFilterOption,
|
||||
activePullRequestAuthorObj,
|
||||
bearerToken
|
||||
}: PullRequestsContentHeaderProps) {
|
||||
const { getString } = useStrings()
|
||||
const [authorFilterOption, setAuthorFilterOption] = useState(activePullRequestAuthorFilterOption)
|
||||
|
||||
const [query, setQuery] = useState<string>('')
|
||||
const [loadingAuthors, setLoadingAuthors] = useState<boolean>(false)
|
||||
const { currentUser, routingId } = useAppContext()
|
||||
|
||||
const moveCurrentUserToTop = async (
|
||||
authorsList: TypesPrincipalInfo[],
|
||||
user: Required<TypesUser>,
|
||||
userQuery: string
|
||||
): Promise<TypesPrincipalInfo[]> => {
|
||||
const sortedList = sortBy(authorsList, item => item.display_name?.toLowerCase())
|
||||
const updateList = (index: number, list: TypesPrincipalInfo[]) => {
|
||||
if (index !== -1) {
|
||||
const currentUserObj = list[index]
|
||||
list.splice(index, 1)
|
||||
list.unshift(currentUserObj)
|
||||
}
|
||||
}
|
||||
if (userQuery) return sortedList
|
||||
const targetIndex = sortedList.findIndex(obj => obj.uid === user.uid)
|
||||
if (targetIndex !== -1) {
|
||||
updateList(targetIndex, sortedList)
|
||||
} else {
|
||||
if (user) {
|
||||
const newAuthorsList = await getUsingFetch(getConfig('code/api/v1'), `/principals`, bearerToken, {
|
||||
queryParams: {
|
||||
query: user?.display_name?.trim(),
|
||||
type: 'user',
|
||||
accountIdentifier: routingId
|
||||
}
|
||||
})
|
||||
const mergedList = [...new Set(authorsList?.concat(newAuthorsList))]
|
||||
const newSortedList = sortBy(mergedList, item => item.display_name?.toLowerCase())
|
||||
const newIndex = newSortedList.findIndex(obj => obj.uid === user.uid)
|
||||
updateList(newIndex, newSortedList)
|
||||
return newSortedList
|
||||
}
|
||||
}
|
||||
return sortedList
|
||||
}
|
||||
|
||||
const getAuthorsPromise = async (): Promise<SelectOption[]> => {
|
||||
setLoadingAuthors(true)
|
||||
try {
|
||||
const fetchedAuthors: TypesPrincipalInfo[] = await getUsingFetch(
|
||||
getConfig('code/api/v1'),
|
||||
`/principals`,
|
||||
bearerToken,
|
||||
{
|
||||
queryParams: {
|
||||
query: query?.trim(),
|
||||
type: 'user',
|
||||
accountIdentifier: routingId
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const authors = [...fetchedAuthors, ...(activePullRequestAuthorObj ? [activePullRequestAuthorObj] : [])]
|
||||
|
||||
const authorsList = await moveCurrentUserToTop(authors, currentUser, query)
|
||||
|
||||
const updatedAuthorsList = Array.isArray(authorsList)
|
||||
? ([
|
||||
...(authorsList || []).map(item => ({
|
||||
label: JSON.stringify({ displayName: item?.display_name, email: item?.email }),
|
||||
value: String(item?.id)
|
||||
}))
|
||||
] as SelectOption[])
|
||||
: ([] as SelectOption[])
|
||||
setLoadingAuthors(false)
|
||||
return updatedAuthorsList
|
||||
} catch (error) {
|
||||
setLoadingAuthors(false)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<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={getString('filterByAuthor')}
|
||||
addClearBtn={true}
|
||||
resetOnClose
|
||||
resetOnSelect
|
||||
resetOnQuery
|
||||
query={query}
|
||||
onQueryChange={newQuery => {
|
||||
setQuery(newQuery)
|
||||
}}
|
||||
itemRenderer={(item, { handleClick }) => {
|
||||
const itemObj = JSON.parse(item.label)
|
||||
return (
|
||||
<Layout.Horizontal
|
||||
padding={{ top: 'small', right: 'small', bottom: 'small', left: 'small' }}
|
||||
font={{ variation: FontVariation.BODY }}
|
||||
className={css.authorDropdownItem}
|
||||
onClick={handleClick}>
|
||||
<Text color={Color.GREY_900} className={css.authorName} tooltipProps={{ isDark: true }}>
|
||||
<span>{itemObj.displayName}</span>
|
||||
</Text>
|
||||
<Text color={Color.GREY_400} font={{ variation: FontVariation.BODY }} lineClamp={1} tooltip={itemObj.email}>
|
||||
({itemObj.email})
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
)
|
||||
}}
|
||||
getCustomLabel={item => {
|
||||
const itemObj = JSON.parse(item.label)
|
||||
return (
|
||||
<Layout.Horizontal spacing="small">
|
||||
<Text
|
||||
color={Color.GREY_900}
|
||||
font={{ variation: FontVariation.BODY }}
|
||||
tooltip={
|
||||
<Text
|
||||
padding={{ top: 'medium', right: 'medium', bottom: 'medium', left: 'medium' }}
|
||||
color={Color.GREY_0}>
|
||||
{itemObj.email}
|
||||
</Text>
|
||||
}
|
||||
tooltipProps={{ isDark: true }}>
|
||||
{itemObj.displayName}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -16,28 +16,17 @@
|
|||
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Container,
|
||||
Layout,
|
||||
FlexExpander,
|
||||
DropDown,
|
||||
ButtonVariation,
|
||||
Button,
|
||||
SelectOption,
|
||||
Text
|
||||
} from '@harnessio/uicore'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import { sortBy } from 'lodash-es'
|
||||
import { getConfig, getUsingFetch } from 'services/config'
|
||||
import { Container, Layout, FlexExpander, DropDown, ButtonVariation, Button } from '@harnessio/uicore'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { CodeIcon, GitInfoProps, makeDiffRefs, PullRequestFilterOption } from 'utils/GitUtils'
|
||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||
import type { TypesPrincipalInfo, TypesUser } from 'services/code'
|
||||
import type { TypesPrincipalInfo } from 'services/code'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
|
||||
import { LabelFilterObj, PageBrowserProps, permissionProps } from 'utils/Utils'
|
||||
import { LabelFilterObj, PageBrowserProps, ScopeLevel, permissionProps } from 'utils/Utils'
|
||||
import { useQueryParams } from 'hooks/useQueryParams'
|
||||
import { LabelFilter } from 'components/Label/LabelFilter/LabelFilter'
|
||||
import { PRAuthorFilter } from './PRAuthorFilter'
|
||||
import css from './PullRequestsContentHeader.module.scss'
|
||||
|
||||
interface PullRequestsContentHeaderProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
||||
|
@ -68,13 +57,10 @@ export function PullRequestsContentHeader({
|
|||
const { getString } = useStrings()
|
||||
const browserParams = useQueryParams<PageBrowserProps>()
|
||||
const [filterOption, setFilterOption] = useState(activePullRequestFilterOption)
|
||||
const [authorFilterOption, setAuthorFilterOption] = useState(activePullRequestAuthorFilterOption)
|
||||
const [labelFilterOption, setLabelFilterOption] = useState(activePullRequestLabelFilterOption)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [query, setQuery] = useState<string>('')
|
||||
const [loadingAuthors, setLoadingAuthors] = useState<boolean>(false)
|
||||
const space = useGetSpaceParam()
|
||||
const { hooks, currentUser, standalone, routingId, routes } = useAppContext()
|
||||
const { hooks, standalone, routes } = useAppContext()
|
||||
const permPushResult = hooks?.usePermissionTranslate?.(
|
||||
{
|
||||
resource: {
|
||||
|
@ -99,85 +85,12 @@ export function PullRequestsContentHeader({
|
|||
{ label: getString('open'), value: PullRequestFilterOption.OPEN },
|
||||
{ label: getString('merged'), value: PullRequestFilterOption.MERGED },
|
||||
{ label: getString('closed'), value: PullRequestFilterOption.CLOSED },
|
||||
// { label: getString('draft'), value: PullRequestFilterOption.DRAFT },
|
||||
// { label: getString('yours'), value: PullRequestFilterOption.YOURS },
|
||||
{ label: getString('all'), value: PullRequestFilterOption.ALL }
|
||||
],
|
||||
[getString]
|
||||
)
|
||||
|
||||
const bearerToken = hooks?.useGetToken?.() || ''
|
||||
const moveCurrentUserToTop = async (
|
||||
authorsList: TypesPrincipalInfo[],
|
||||
user: Required<TypesUser>,
|
||||
userQuery: string
|
||||
): Promise<TypesPrincipalInfo[]> => {
|
||||
const sortedList = sortBy(authorsList, item => item.display_name?.toLowerCase())
|
||||
const updateList = (index: number, list: TypesPrincipalInfo[]) => {
|
||||
if (index !== -1) {
|
||||
const currentUserObj = list[index]
|
||||
list.splice(index, 1)
|
||||
list.unshift(currentUserObj)
|
||||
}
|
||||
}
|
||||
if (userQuery) return sortedList
|
||||
const targetIndex = sortedList.findIndex(obj => obj.uid === user.uid)
|
||||
if (targetIndex !== -1) {
|
||||
updateList(targetIndex, sortedList)
|
||||
} else {
|
||||
if (user) {
|
||||
const newAuthorsList = await getUsingFetch(getConfig('code/api/v1'), `/principals`, bearerToken, {
|
||||
queryParams: {
|
||||
query: user?.display_name?.trim(),
|
||||
type: 'user',
|
||||
accountIdentifier: routingId
|
||||
}
|
||||
})
|
||||
const mergedList = [...new Set(authorsList?.concat(newAuthorsList))]
|
||||
const newSortedList = sortBy(mergedList, item => item.display_name?.toLowerCase())
|
||||
const newIndex = newSortedList.findIndex(obj => obj.uid === user.uid)
|
||||
updateList(newIndex, newSortedList)
|
||||
return newSortedList
|
||||
}
|
||||
}
|
||||
return sortedList
|
||||
}
|
||||
|
||||
const getAuthorsPromise = async (): Promise<SelectOption[]> => {
|
||||
setLoadingAuthors(true)
|
||||
try {
|
||||
const fetchedAuthors: TypesPrincipalInfo[] = await getUsingFetch(
|
||||
getConfig('code/api/v1'),
|
||||
`/principals`,
|
||||
bearerToken,
|
||||
{
|
||||
queryParams: {
|
||||
query: query?.trim(),
|
||||
type: 'user',
|
||||
accountIdentifier: routingId
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const authors = [...fetchedAuthors, ...(activePullRequestAuthorObj ? [activePullRequestAuthorObj] : [])]
|
||||
|
||||
const authorsList = await moveCurrentUserToTop(authors, currentUser, query)
|
||||
|
||||
const updatedAuthorsList = Array.isArray(authorsList)
|
||||
? ([
|
||||
...(authorsList || []).map(item => ({
|
||||
label: JSON.stringify({ displayName: item?.display_name, email: item?.email }),
|
||||
value: String(item?.id)
|
||||
}))
|
||||
] as SelectOption[])
|
||||
: ([] as SelectOption[])
|
||||
setLoadingAuthors(false)
|
||||
return updatedAuthorsList
|
||||
} catch (error) {
|
||||
setLoadingAuthors(false)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className={css.main} padding="xlarge">
|
||||
|
@ -200,70 +113,16 @@ export function PullRequestsContentHeader({
|
|||
bearerToken={bearerToken}
|
||||
repoMetadata={repoMetadata}
|
||||
spaceRef={space}
|
||||
filterScope={ScopeLevel.REPOSITORY}
|
||||
/>
|
||||
|
||||
<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={getString('selectAuthor')}
|
||||
addClearBtn={true}
|
||||
resetOnClose
|
||||
resetOnSelect
|
||||
resetOnQuery
|
||||
query={query}
|
||||
onQueryChange={newQuery => {
|
||||
setQuery(newQuery)
|
||||
}}
|
||||
itemRenderer={(item, { handleClick }) => {
|
||||
const itemObj = JSON.parse(item.label)
|
||||
return (
|
||||
<Layout.Horizontal
|
||||
padding={{ top: 'small', right: 'small', bottom: 'small', left: 'small' }}
|
||||
font={{ variation: FontVariation.BODY }}
|
||||
className={css.authorDropdownItem}
|
||||
onClick={handleClick}>
|
||||
<Text color={Color.GREY_900} className={css.authorName} tooltipProps={{ isDark: true }}>
|
||||
<span>{itemObj.displayName}</span>
|
||||
</Text>
|
||||
<Text
|
||||
color={Color.GREY_400}
|
||||
font={{ variation: FontVariation.BODY }}
|
||||
lineClamp={1}
|
||||
tooltip={itemObj.email}>
|
||||
({itemObj.email})
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
)
|
||||
}}
|
||||
getCustomLabel={item => {
|
||||
const itemObj = JSON.parse(item.label)
|
||||
return (
|
||||
<Layout.Horizontal spacing="small">
|
||||
<Text
|
||||
color={Color.GREY_900}
|
||||
font={{ variation: FontVariation.BODY }}
|
||||
tooltip={
|
||||
<Text
|
||||
padding={{ top: 'medium', right: 'medium', bottom: 'medium', left: 'medium' }}
|
||||
color={Color.GREY_0}>
|
||||
{itemObj.email}
|
||||
</Text>
|
||||
}
|
||||
tooltipProps={{ isDark: true }}>
|
||||
{itemObj.displayName}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
)
|
||||
}}
|
||||
<PRAuthorFilter
|
||||
onPullRequestAuthorFilterChanged={onPullRequestAuthorFilterChanged}
|
||||
activePullRequestAuthorFilterOption={activePullRequestAuthorFilterOption}
|
||||
activePullRequestAuthorObj={activePullRequestAuthorObj}
|
||||
bearerToken={bearerToken}
|
||||
/>
|
||||
|
||||
<DropDown
|
||||
value={filterOption}
|
||||
items={items}
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Container, Layout, FlexExpander, DropDown, SelectOption } from '@harnessio/uicore'
|
||||
import { Render } from 'react-jsx-match'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { PullRequestFilterOption, PullRequestReviewFilterOption, SpacePRTabs } from 'utils/GitUtils'
|
||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||
import type { TypesPrincipalInfo } from 'services/code'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
|
||||
import { ScopeLevelEnum, type LabelFilterObj, type PageBrowserProps, ScopeLevel } from 'utils/Utils'
|
||||
import { useQueryParams } from 'hooks/useQueryParams'
|
||||
import { LabelFilter } from 'components/Label/LabelFilter/LabelFilter'
|
||||
import { PRAuthorFilter } from './PRAuthorFilter'
|
||||
import css from './PullRequestsContentHeader.module.scss'
|
||||
|
||||
interface SpacePullRequestsContentHeaderProps {
|
||||
activeTab: SpacePRTabs
|
||||
loading?: boolean
|
||||
activePullRequestFilterOption?: string
|
||||
activePullRequestReviewFilterOption?: string
|
||||
activePullRequestAuthorFilterOption?: string
|
||||
activePullRequestAuthorObj?: TypesPrincipalInfo | null
|
||||
activePullRequestLabelFilterOption?: LabelFilterObj[]
|
||||
activePullRequestIncludeSubSpaceOption?: ScopeLevelEnum
|
||||
onPullRequestFilterChanged: React.Dispatch<React.SetStateAction<string>>
|
||||
onPullRequestReviewFilterChanged: React.Dispatch<React.SetStateAction<string>>
|
||||
onPullRequestAuthorFilterChanged: (authorFilter: string) => void
|
||||
onPullRequestLabelFilterChanged: (labelFilter: LabelFilterObj[]) => void
|
||||
onSearchTermChanged: (searchTerm: string) => void
|
||||
onPullRequestIncludeSubSpaceOptionChanged: React.Dispatch<React.SetStateAction<ScopeLevelEnum>>
|
||||
}
|
||||
|
||||
export function SpacePullRequestsContentHeader({
|
||||
activeTab,
|
||||
loading,
|
||||
onPullRequestFilterChanged,
|
||||
onPullRequestReviewFilterChanged,
|
||||
onPullRequestAuthorFilterChanged,
|
||||
onPullRequestLabelFilterChanged,
|
||||
onPullRequestIncludeSubSpaceOptionChanged,
|
||||
onSearchTermChanged,
|
||||
activePullRequestFilterOption = PullRequestFilterOption.OPEN,
|
||||
activePullRequestReviewFilterOption,
|
||||
activePullRequestAuthorFilterOption,
|
||||
activePullRequestLabelFilterOption,
|
||||
activePullRequestAuthorObj,
|
||||
activePullRequestIncludeSubSpaceOption
|
||||
}: SpacePullRequestsContentHeaderProps) {
|
||||
const { getString } = useStrings()
|
||||
const browserParams = useQueryParams<PageBrowserProps>()
|
||||
const [filterOption, setFilterOption] = useState(activePullRequestFilterOption)
|
||||
const [reviewFilterOption, setReviewFilterOption] = useState(activePullRequestReviewFilterOption)
|
||||
const [labelFilterOption, setLabelFilterOption] = useState(activePullRequestLabelFilterOption)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const space = useGetSpaceParam()
|
||||
const { hooks } = useAppContext()
|
||||
const [accountIdentifier, orgIdentifier, projectIdentifier] = space?.split('/') || []
|
||||
|
||||
useEffect(() => {
|
||||
setLabelFilterOption(activePullRequestLabelFilterOption)
|
||||
}, [activePullRequestLabelFilterOption])
|
||||
|
||||
useEffect(() => {
|
||||
setFilterOption(browserParams?.state as string)
|
||||
}, [browserParams, activeTab])
|
||||
|
||||
useEffect(() => {
|
||||
activeTab === SpacePRTabs.REVIEW_REQUESTED && setReviewFilterOption(activePullRequestReviewFilterOption)
|
||||
}, [activePullRequestReviewFilterOption, activeTab])
|
||||
|
||||
const items = useMemo(
|
||||
() => [
|
||||
{ label: getString('open'), value: PullRequestFilterOption.OPEN },
|
||||
{ label: getString('merged'), value: PullRequestFilterOption.MERGED },
|
||||
{ label: getString('closed'), value: PullRequestFilterOption.CLOSED },
|
||||
{ label: getString('all'), value: PullRequestFilterOption.ALL }
|
||||
],
|
||||
[getString]
|
||||
)
|
||||
|
||||
const reviewItems = useMemo(
|
||||
() => [
|
||||
{ label: getString('pending'), value: PullRequestReviewFilterOption.PENDING },
|
||||
{ label: getString('approved'), value: PullRequestReviewFilterOption.APPROVED },
|
||||
{ label: getString('pr.changesRequested'), value: PullRequestReviewFilterOption.CHANGES_REQUESTED }
|
||||
],
|
||||
[getString]
|
||||
)
|
||||
|
||||
const bearerToken = hooks?.useGetToken?.() || ''
|
||||
|
||||
const scopeOption = [
|
||||
accountIdentifier && !orgIdentifier
|
||||
? {
|
||||
label: getString('searchScope.allScopes'),
|
||||
value: ScopeLevelEnum.ALL
|
||||
}
|
||||
: null,
|
||||
accountIdentifier && !orgIdentifier
|
||||
? { label: getString('searchScope.accOnly'), value: ScopeLevelEnum.CURRENT }
|
||||
: null,
|
||||
orgIdentifier ? { label: getString('searchScope.orgAndProj'), value: ScopeLevelEnum.ALL } : null,
|
||||
orgIdentifier ? { label: getString('searchScope.orgOnly'), value: ScopeLevelEnum.CURRENT } : null
|
||||
].filter(Boolean) as SelectOption[]
|
||||
|
||||
const currentScopeLabel =
|
||||
activePullRequestIncludeSubSpaceOption === ScopeLevelEnum.ALL
|
||||
? {
|
||||
label:
|
||||
accountIdentifier && !orgIdentifier
|
||||
? getString('searchScope.allScopes')
|
||||
: getString('searchScope.orgAndProj'),
|
||||
value: ScopeLevelEnum.ALL
|
||||
}
|
||||
: {
|
||||
label:
|
||||
accountIdentifier && !orgIdentifier ? getString('searchScope.accOnly') : getString('searchScope.orgOnly'),
|
||||
value: ScopeLevelEnum.CURRENT
|
||||
}
|
||||
|
||||
const [scopeLabel, setScopeLabel] = useState<SelectOption>(currentScopeLabel ? currentScopeLabel : scopeOption[0])
|
||||
|
||||
return (
|
||||
<Container className={css.main} padding="xlarge">
|
||||
<Layout.Horizontal spacing="medium">
|
||||
<SearchInputWithSpinner
|
||||
loading={loading}
|
||||
spinnerPosition="right"
|
||||
query={searchTerm}
|
||||
setQuery={value => {
|
||||
setSearchTerm(value)
|
||||
onSearchTermChanged(value)
|
||||
}}
|
||||
/>
|
||||
<Render when={!projectIdentifier}>
|
||||
<DropDown
|
||||
placeholder={scopeLabel.label}
|
||||
value={scopeLabel}
|
||||
items={scopeOption}
|
||||
onChange={e => {
|
||||
onPullRequestIncludeSubSpaceOptionChanged(e.value as ScopeLevelEnum)
|
||||
setScopeLabel(e)
|
||||
}}
|
||||
/>
|
||||
</Render>
|
||||
<FlexExpander />
|
||||
|
||||
<LabelFilter
|
||||
labelFilterOption={labelFilterOption}
|
||||
setLabelFilterOption={setLabelFilterOption}
|
||||
onPullRequestLabelFilterChanged={onPullRequestLabelFilterChanged}
|
||||
bearerToken={bearerToken}
|
||||
filterScope={ScopeLevel.SPACE}
|
||||
spaceRef={space}
|
||||
/>
|
||||
<Render when={activeTab === SpacePRTabs.REVIEW_REQUESTED}>
|
||||
<DropDown
|
||||
value={reviewFilterOption}
|
||||
items={reviewItems}
|
||||
onChange={({ value }) => {
|
||||
setReviewFilterOption(value as string)
|
||||
onPullRequestReviewFilterChanged(value as string)
|
||||
}}
|
||||
popoverClassName={css.branchDropdown}
|
||||
/>
|
||||
</Render>
|
||||
<Render when={activeTab !== SpacePRTabs.CREATED}>
|
||||
<PRAuthorFilter
|
||||
onPullRequestAuthorFilterChanged={onPullRequestAuthorFilterChanged}
|
||||
activePullRequestAuthorFilterOption={activePullRequestAuthorFilterOption}
|
||||
activePullRequestAuthorObj={activePullRequestAuthorObj}
|
||||
bearerToken={bearerToken}
|
||||
/>
|
||||
</Render>
|
||||
<DropDown
|
||||
value={filterOption}
|
||||
items={items}
|
||||
onChange={({ value }) => {
|
||||
setFilterOption(value as string)
|
||||
onPullRequestFilterChanged(value as string)
|
||||
}}
|
||||
popoverClassName={css.branchDropdown}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,537 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Container,
|
||||
PageBody,
|
||||
Text,
|
||||
TableV2,
|
||||
Layout,
|
||||
StringSubstitute,
|
||||
FlexExpander,
|
||||
Utils,
|
||||
stringSubstitute
|
||||
} from '@harnessio/uicore'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useGet } from 'restful-react'
|
||||
import type { CellProps, Column } from 'react-table'
|
||||
import { Case, Match, Render, Truthy } from 'react-jsx-match'
|
||||
import { defaultTo, isEmpty, noop } from 'lodash-es'
|
||||
import { PullRequestFilterOption, PullRequestReviewFilterOption, PullRequestState, SpacePRTabs } from 'utils/GitUtils'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import {
|
||||
voidFn,
|
||||
getErrorMessage,
|
||||
LIST_FETCHING_LIMIT,
|
||||
PageBrowserProps,
|
||||
ColorName,
|
||||
LabelFilterObj,
|
||||
LabelFilterType,
|
||||
ScopeLevelEnum,
|
||||
PageAction
|
||||
} from 'utils/Utils'
|
||||
import { usePageIndex } from 'hooks/usePageIndex'
|
||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||
import { useUpdateQueryParams } from 'hooks/useUpdateQueryParams'
|
||||
import { useQueryParams } from 'hooks/useQueryParams'
|
||||
import type { TypesLabelPullReqAssignmentInfo, TypesPrincipalInfo, TypesPullReqRepo } from 'services/code'
|
||||
import { PrevNextPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||
import { NoResultCard } from 'components/NoResultCard/NoResultCard'
|
||||
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
|
||||
import { GitRefLink } from 'components/GitRefLink/GitRefLink'
|
||||
import { PullRequestStateLabel } from 'components/PullRequestStateLabel/PullRequestStateLabel'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||
import { TimePopoverWithLocal } from 'utils/timePopoverLocal/TimePopoverWithLocal'
|
||||
import { Label } from 'components/Label/Label'
|
||||
import { getConfig } from 'services/config'
|
||||
import { SpacePullRequestsContentHeader } from './PullRequestsContentHeader/SpacePullRequestsContentHeader'
|
||||
import css from './PullRequests.module.scss'
|
||||
|
||||
interface SpacePullRequestsProps {
|
||||
activeTab: SpacePRTabs
|
||||
includeSubspaces: ScopeLevelEnum
|
||||
setIncludeSubspaces: (sub: ScopeLevelEnum) => void
|
||||
}
|
||||
|
||||
export function SpacePullRequestsListing({ activeTab, includeSubspaces, setIncludeSubspaces }: SpacePullRequestsProps) {
|
||||
const { getString } = useStrings()
|
||||
const { routes, routingId, currentUser } = useAppContext()
|
||||
const [searchTerm, setSearchTerm] = useState<string | undefined>()
|
||||
const browserParams = useQueryParams<PageBrowserProps>()
|
||||
const [filter, setFilter] = useState(browserParams?.state || (PullRequestFilterOption.OPEN as string))
|
||||
const [reviewFilter, setReviewFilter] = useState(
|
||||
browserParams?.review || (PullRequestReviewFilterOption.PENDING as string)
|
||||
)
|
||||
const [authorFilter, setAuthorFilter] = useState<string>(browserParams?.author ?? '')
|
||||
const [labelFilter, setLabelFilter] = useState<LabelFilterObj[]>([])
|
||||
const [pageAction, setPageAction] = useState<{ action: PageAction; timestamp: number }>({
|
||||
action: PageAction.NEXT,
|
||||
timestamp: 1
|
||||
})
|
||||
const space = useGetSpaceParam()
|
||||
const { updateQueryParams, replaceQueryParams } = useUpdateQueryParams()
|
||||
const pageInit = browserParams.page ? parseInt(browserParams.page) : 1
|
||||
const [page, setPage] = usePageIndex(pageInit)
|
||||
|
||||
const { data: principal, refetch: refetchPrincipal } = useGet<TypesPrincipalInfo>({
|
||||
base: getConfig('code/api/v1'),
|
||||
path: `/principals/${browserParams.author}`,
|
||||
queryParams: {
|
||||
accountIdentifier: routingId
|
||||
},
|
||||
lazy: true
|
||||
})
|
||||
useEffect(() => {
|
||||
const params = {
|
||||
...browserParams,
|
||||
...(Boolean(authorFilter) && { author: authorFilter }),
|
||||
// ...(page >= 1 && { page: page.toString() }),
|
||||
...(filter && { state: filter }),
|
||||
...(reviewFilter && { review: reviewFilter }),
|
||||
subspace: includeSubspaces.toString()
|
||||
}
|
||||
|
||||
updateQueryParams(params, undefined, true)
|
||||
|
||||
if (page <= 1) {
|
||||
const updateParams = { ...params }
|
||||
delete updateParams.page
|
||||
replaceQueryParams(updateParams, undefined, true)
|
||||
}
|
||||
|
||||
if (!authorFilter && browserParams.author) {
|
||||
const paramList = { ...params }
|
||||
delete paramList.author
|
||||
replaceQueryParams(paramList, undefined, true)
|
||||
}
|
||||
|
||||
if (activeTab === SpacePRTabs.CREATED || !reviewFilter) {
|
||||
const paramList = { ...params }
|
||||
delete paramList.review
|
||||
replaceQueryParams(paramList, undefined, true)
|
||||
}
|
||||
|
||||
if (browserParams.author) {
|
||||
refetchPrincipal()
|
||||
}
|
||||
}, [page, filter, reviewFilter, authorFilter, activeTab, includeSubspaces]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const [accountIdentifier, orgIdentifier, projectIdentifier] = space?.split('/') || []
|
||||
|
||||
const {
|
||||
data,
|
||||
error: prError,
|
||||
loading: prLoading,
|
||||
refetch: refetchPrs
|
||||
} = useGet<TypesPullReqRepo[]>({
|
||||
//add type
|
||||
path: `/api/v1/pullreq`,
|
||||
queryParams: {
|
||||
accountIdentifier,
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
limit: String(LIST_FETCHING_LIMIT),
|
||||
exclude_description: true,
|
||||
page,
|
||||
sort: filter == PullRequestFilterOption.MERGED ? 'merged' : 'number',
|
||||
order: 'desc',
|
||||
query: searchTerm,
|
||||
include_subspaces: includeSubspaces === ScopeLevelEnum.ALL,
|
||||
state: browserParams.state ? browserParams.state : filter == PullRequestFilterOption.ALL ? '' : filter,
|
||||
...(activeTab === SpacePRTabs.REVIEW_REQUESTED && {
|
||||
reviewer_id: Number(currentUser.id),
|
||||
review_decision: reviewFilter
|
||||
}),
|
||||
...(activeTab === SpacePRTabs.CREATED && { created_by: Number(currentUser.id) }),
|
||||
...(authorFilter && activeTab === SpacePRTabs.REVIEW_REQUESTED && { created_by: Number(authorFilter) }),
|
||||
|
||||
...(labelFilter.filter(({ type, valueId }) => type === 'label' || valueId === -1).length && {
|
||||
label_id: labelFilter
|
||||
.filter(({ type, valueId }) => type === 'label' || valueId === -1)
|
||||
.map(({ labelId }) => labelId)
|
||||
}),
|
||||
...(labelFilter.filter(({ type }) => type === 'value').length && {
|
||||
value_id: labelFilter
|
||||
.filter(({ type, valueId }) => type === 'value' && valueId !== -1)
|
||||
.map(({ valueId }) => valueId)
|
||||
}),
|
||||
|
||||
...(page > 1
|
||||
? pageAction?.action === PageAction.NEXT
|
||||
? { updated_lt: pageAction.timestamp }
|
||||
: { updated_gt: pageAction.timestamp }
|
||||
: {})
|
||||
},
|
||||
queryParamStringifyOptions: {
|
||||
arrayFormat: 'repeat'
|
||||
},
|
||||
debounce: 500,
|
||||
lazy: !currentUser
|
||||
})
|
||||
|
||||
const handleLabelClick = (labelFilterArr: LabelFilterObj[], clickedLabel: TypesLabelPullReqAssignmentInfo) => {
|
||||
// if not present - add :
|
||||
const isLabelAlreadyAdded = labelFilterArr.map(({ labelId }) => labelId).includes(clickedLabel.id || -1)
|
||||
const updatedLabelsList = [...labelFilterArr]
|
||||
if (!isLabelAlreadyAdded && clickedLabel?.id) {
|
||||
if (clickedLabel.value && clickedLabel.value_id) {
|
||||
updatedLabelsList.push({
|
||||
labelId: clickedLabel.id,
|
||||
type: LabelFilterType.VALUE,
|
||||
valueId: clickedLabel.value_id,
|
||||
labelObj: clickedLabel,
|
||||
valueObj: {
|
||||
id: clickedLabel.value_id,
|
||||
color: clickedLabel.value_color,
|
||||
label_id: clickedLabel.id,
|
||||
value: clickedLabel.value
|
||||
}
|
||||
})
|
||||
} else if (clickedLabel.value_count && !clickedLabel.value_id) {
|
||||
updatedLabelsList.push({
|
||||
labelId: clickedLabel.id,
|
||||
type: LabelFilterType.VALUE,
|
||||
valueId: -1,
|
||||
labelObj: clickedLabel,
|
||||
valueObj: {
|
||||
id: -1,
|
||||
color: clickedLabel.value_color,
|
||||
label_id: clickedLabel.id,
|
||||
value: getString('labels.anyValueOption')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
updatedLabelsList.push({
|
||||
labelId: clickedLabel.id,
|
||||
type: LabelFilterType.LABEL,
|
||||
valueId: undefined,
|
||||
labelObj: clickedLabel,
|
||||
valueObj: undefined
|
||||
})
|
||||
}
|
||||
setLabelFilter(updatedLabelsList)
|
||||
}
|
||||
|
||||
// if 'any value' label present - replace :
|
||||
const replacedAnyValueIfPresent = updatedLabelsList.map(filterObj => {
|
||||
if (
|
||||
filterObj.valueId === -1 &&
|
||||
filterObj.labelId === clickedLabel.id &&
|
||||
clickedLabel.value_id &&
|
||||
clickedLabel.value
|
||||
) {
|
||||
return {
|
||||
...filterObj,
|
||||
valueId: clickedLabel.value_id,
|
||||
valueObj: {
|
||||
id: clickedLabel.value_id,
|
||||
color: clickedLabel.value_color,
|
||||
label_id: clickedLabel.id,
|
||||
value: clickedLabel.value
|
||||
}
|
||||
}
|
||||
}
|
||||
return filterObj
|
||||
})
|
||||
const isUpdated = !updatedLabelsList.every((obj, index) => obj === replacedAnyValueIfPresent[index])
|
||||
if (isUpdated) {
|
||||
setLabelFilter(replacedAnyValueIfPresent)
|
||||
}
|
||||
}
|
||||
|
||||
const columns: Column<TypesPullReqRepo>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'title',
|
||||
width: '100%',
|
||||
Cell: ({ row }: CellProps<TypesPullReqRepo>) => {
|
||||
//add type
|
||||
const { pull_request, repository } = row.original
|
||||
return (
|
||||
<Link
|
||||
className={css.rowLink}
|
||||
to={routes.toCODEPullRequest({
|
||||
repoPath: row?.original?.repository?.path as string,
|
||||
pullRequestId: String(pull_request?.number)
|
||||
})}>
|
||||
<Layout.Horizontal className={css.titleRow} spacing="medium">
|
||||
<PullRequestStateLabel iconSize={22} data={pull_request} iconOnly />
|
||||
<Container padding={{ left: 'small' }}>
|
||||
<Layout.Vertical spacing="small">
|
||||
<Container>
|
||||
<Layout.Horizontal flex={{ alignItems: 'center' }} className={css.prLabels}>
|
||||
<Layout.Horizontal spacing={'xsmall'}>
|
||||
<Text
|
||||
icon="code-repo"
|
||||
font={{ variation: FontVariation.SMALL_SEMI }}
|
||||
color={Color.GREY_600}
|
||||
border={{ right: true }}
|
||||
padding={{ right: 'small' }}>
|
||||
{repository?.identifier}
|
||||
</Text>
|
||||
<Text padding={{ left: 'xsmall' }} color={Color.GREY_800} className={css.title} lineClamp={1}>
|
||||
{pull_request?.title}
|
||||
</Text>
|
||||
|
||||
<Container className={css.convo}>
|
||||
<Icon
|
||||
className={css.convoIcon}
|
||||
padding={{ left: 'small', right: 'small' }}
|
||||
name="code-chat"
|
||||
size={15}
|
||||
/>
|
||||
<Text font={{ variation: FontVariation.SMALL }} color={Color.GREY_500} tag="span">
|
||||
{pull_request?.stats?.conversations}
|
||||
</Text>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
<Render when={row.original && pull_request?.labels && pull_request.labels.length !== 0}>
|
||||
{row.original?.pull_request?.labels?.map((label, index: number) => (
|
||||
<Label
|
||||
key={index}
|
||||
name={label.key as string}
|
||||
label_color={label.color as ColorName}
|
||||
label_value={{
|
||||
name: label.value as string,
|
||||
color: label.value_color as ColorName
|
||||
}}
|
||||
scope={label.scope}
|
||||
onClick={() => {
|
||||
handleLabelClick(labelFilter, label)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Render>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
<Container>
|
||||
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
||||
<Text color={Color.GREY_500} font={{ size: 'small' }}>
|
||||
<StringSubstitute
|
||||
str={getString('pr.statusLine')}
|
||||
vars={{
|
||||
state: pull_request?.state,
|
||||
number: <Text inline>{pull_request?.number}</Text>,
|
||||
time: (
|
||||
<strong>
|
||||
<TimePopoverWithLocal
|
||||
time={defaultTo(
|
||||
(pull_request?.state == PullRequestState.MERGED
|
||||
? pull_request?.merged
|
||||
: pull_request?.created) as number,
|
||||
0
|
||||
)}
|
||||
inline={false}
|
||||
font={{ variation: FontVariation.SMALL_BOLD }}
|
||||
color={Color.GREY_500}
|
||||
tag="span"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
user: (
|
||||
<strong>
|
||||
{pull_request?.author?.display_name || pull_request?.author?.email || ''}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
<PipeSeparator height={10} />
|
||||
<Container>
|
||||
<Layout.Horizontal
|
||||
spacing="xsmall"
|
||||
style={{ alignItems: 'center' }}
|
||||
onClick={Utils.stopEvent}>
|
||||
<GitRefLink
|
||||
text={pull_request?.target_branch as string}
|
||||
url={routes.toCODERepository({
|
||||
repoPath: repository?.path as string,
|
||||
gitRef: pull_request?.target_branch
|
||||
})}
|
||||
showCopy={false}
|
||||
/>
|
||||
<Text color={Color.GREY_500}>←</Text>
|
||||
<GitRefLink
|
||||
text={pull_request?.source_branch as string}
|
||||
url={routes.toCODERepository({
|
||||
repoPath: repository?.path as string,
|
||||
gitRef: pull_request?.source_branch
|
||||
})}
|
||||
showCopy={false}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
<FlexExpander />
|
||||
</Layout.Horizontal>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
}
|
||||
],
|
||||
[getString] // eslint-disable-line react-hooks/exhaustive-deps
|
||||
)
|
||||
return (
|
||||
<Container className={css.main}>
|
||||
<PageBody error={getErrorMessage(prError)} retryOnError={voidFn(refetchPrs)}>
|
||||
<LoadingSpinner visible={prLoading && !searchTerm} withBorder={!searchTerm} />
|
||||
|
||||
<Render when={data}>
|
||||
<Layout.Vertical>
|
||||
<SpacePullRequestsContentHeader
|
||||
activeTab={activeTab}
|
||||
loading={prLoading && searchTerm !== undefined}
|
||||
activePullRequestFilterOption={filter}
|
||||
activePullRequestReviewFilterOption={reviewFilter}
|
||||
onPullRequestFilterChanged={_filter => {
|
||||
setFilter(_filter)
|
||||
setPage(1)
|
||||
}}
|
||||
onPullRequestReviewFilterChanged={_reviewFilter => {
|
||||
setReviewFilter(_reviewFilter)
|
||||
setPage(1)
|
||||
}}
|
||||
onSearchTermChanged={value => {
|
||||
setSearchTerm(value)
|
||||
setPage(1)
|
||||
}}
|
||||
activePullRequestAuthorObj={principal}
|
||||
activePullRequestAuthorFilterOption={authorFilter}
|
||||
activePullRequestLabelFilterOption={labelFilter}
|
||||
activePullRequestIncludeSubSpaceOption={includeSubspaces}
|
||||
onPullRequestAuthorFilterChanged={_authorFilter => {
|
||||
setAuthorFilter(_authorFilter)
|
||||
setPage(1)
|
||||
}}
|
||||
onPullRequestLabelFilterChanged={_labelFilter => {
|
||||
setLabelFilter(_labelFilter)
|
||||
setPage(1)
|
||||
}}
|
||||
onPullRequestIncludeSubSpaceOptionChanged={_includeSubspaces => {
|
||||
setIncludeSubspaces(_includeSubspaces as ScopeLevelEnum)
|
||||
setPage(1)
|
||||
}}
|
||||
/>
|
||||
<Container padding="xlarge">
|
||||
<Container padding={{ top: 'medium', bottom: 'large' }}>
|
||||
<Layout.Horizontal
|
||||
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}
|
||||
style={{ flexWrap: 'wrap', gap: '5px' }}>
|
||||
<Render when={!isEmpty(labelFilter) || !prLoading}>
|
||||
<Text color={Color.GREY_400}>
|
||||
{isEmpty(data)
|
||||
? !isEmpty(labelFilter) && getString('labels.noResults')
|
||||
: (stringSubstitute(getString('labels.prCount'), {
|
||||
count: data?.length
|
||||
}) as string)}
|
||||
</Text>
|
||||
</Render>
|
||||
|
||||
{labelFilter &&
|
||||
labelFilter?.length !== 0 &&
|
||||
labelFilter?.map((label, index) => (
|
||||
<Label
|
||||
key={index}
|
||||
name={label.labelObj.key as string}
|
||||
label_color={label.labelObj.color as ColorName}
|
||||
label_value={{
|
||||
name: label.valueObj?.value as string,
|
||||
color: label.valueObj?.color as ColorName
|
||||
}}
|
||||
scope={label.labelObj.scope}
|
||||
removeLabelBtn={true}
|
||||
handleRemoveClick={() => {
|
||||
if (label.type === 'value') {
|
||||
const updateFilterObjArr = labelFilter.filter(filterObj => {
|
||||
if (!(filterObj.labelId === label.labelId && filterObj.type === 'value')) {
|
||||
return filterObj
|
||||
}
|
||||
})
|
||||
setLabelFilter(updateFilterObjArr)
|
||||
setPage(1)
|
||||
} else if (label.type === 'label') {
|
||||
const updateFilterObjArr = labelFilter.filter(filterObj => {
|
||||
if (!(filterObj.labelId === label.labelId && filterObj.type === 'label')) {
|
||||
return filterObj
|
||||
}
|
||||
})
|
||||
setLabelFilter(updateFilterObjArr)
|
||||
setPage(1)
|
||||
}
|
||||
}}
|
||||
disableRemoveBtnTooltip={true}
|
||||
/>
|
||||
))}
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
<Match expr={data?.length && !prLoading}>
|
||||
<Truthy>
|
||||
<>
|
||||
<TableV2<any> //add type
|
||||
className={css.table}
|
||||
hideHeaders
|
||||
columns={columns}
|
||||
data={data || []}
|
||||
getRowClassName={() => css.row}
|
||||
onRowClick={noop}
|
||||
/>
|
||||
<PrevNextPagination
|
||||
onPrev={
|
||||
page > 1 && data
|
||||
? () => {
|
||||
setPage(pre => pre - 1)
|
||||
setPageAction({ action: PageAction.PREV, timestamp: data[0].pull_request?.updated ?? 0 })
|
||||
}
|
||||
: false
|
||||
}
|
||||
onNext={
|
||||
data && data?.length === LIST_FETCHING_LIMIT
|
||||
? () => {
|
||||
setPage(pre => pre + 1)
|
||||
setPageAction({
|
||||
action: PageAction.NEXT,
|
||||
timestamp: data.slice(-1)[0]?.pull_request?.updated ?? 0
|
||||
})
|
||||
}
|
||||
: false
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</Truthy>
|
||||
<Case val={0}>
|
||||
<NoResultCard
|
||||
forSearch={!!searchTerm}
|
||||
forFilter={!isEmpty(labelFilter) || !isEmpty(authorFilter) || !isEmpty(filter)}
|
||||
emptyFilterMessage={getString('pullRequestNotFoundforFilter')}
|
||||
message={getString('pullRequestEmpty')}
|
||||
buttonText={getString('newPullRequest')}
|
||||
/>
|
||||
</Case>
|
||||
</Match>
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</Render>
|
||||
</PageBody>
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -1586,6 +1586,17 @@ export interface TypesRepository {
|
|||
updated?: number
|
||||
}
|
||||
|
||||
export interface TypesPullReqLabelAssignInput {
|
||||
label_id?: number
|
||||
value?: string
|
||||
value_id?: number | null
|
||||
}
|
||||
|
||||
export interface TypesPullReqRepo {
|
||||
pull_request?: TypesPullReq
|
||||
repository?: TypesRepository
|
||||
}
|
||||
|
||||
export interface TypesRepositoryPullReqSummary {
|
||||
closed_count?: number
|
||||
merged_count?: number
|
||||
|
@ -1783,6 +1794,7 @@ export interface TypesUser {
|
|||
created?: number
|
||||
display_name?: string
|
||||
email?: string
|
||||
id?: number
|
||||
uid?: string
|
||||
updated?: number
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -133,6 +133,11 @@ export enum SettingsTab {
|
|||
labels = 'labels'
|
||||
}
|
||||
|
||||
export enum SpacePRTabs {
|
||||
CREATED = 'created',
|
||||
REVIEW_REQUESTED = 'review_requested'
|
||||
}
|
||||
|
||||
export enum SpaceSettingsTab {
|
||||
general = '/',
|
||||
labels = 'labels'
|
||||
|
@ -235,6 +240,12 @@ export const PullRequestFilterOption = {
|
|||
ALL: 'all'
|
||||
}
|
||||
|
||||
export const PullRequestReviewFilterOption = {
|
||||
PENDING: 'pending',
|
||||
APPROVED: 'approved',
|
||||
CHANGES_REQUESTED: 'changereq'
|
||||
}
|
||||
|
||||
export enum MergeStrategy {
|
||||
MERGE = 'merge',
|
||||
SQUASH = 'squash',
|
||||
|
|
|
@ -132,6 +132,8 @@ export interface PageBrowserProps {
|
|||
page?: string
|
||||
state?: string
|
||||
tab?: string
|
||||
review?: string
|
||||
subspace?: ScopeLevelEnum
|
||||
}
|
||||
|
||||
export const extractInfoFromRuleViolationArr = (ruleViolationArr: TypesRuleViolations[]) => {
|
||||
|
@ -429,6 +431,11 @@ export const ButtonRoleProps = {
|
|||
style: { cursor: 'pointer ' }
|
||||
}
|
||||
|
||||
export enum ScopeLevel {
|
||||
REPOSITORY = 'repos',
|
||||
SPACE = 'space'
|
||||
}
|
||||
|
||||
export enum orderSortDate {
|
||||
ASC = 'asc',
|
||||
DESC = 'desc'
|
||||
|
@ -680,6 +687,11 @@ export enum ScopeLevelEnum {
|
|||
CURRENT = 'current'
|
||||
}
|
||||
|
||||
export enum PageAction {
|
||||
NEXT = 'next',
|
||||
PREV = 'previous'
|
||||
}
|
||||
|
||||
export enum PullRequestCheckType {
|
||||
EMPTY = '',
|
||||
RAW = 'raw',
|
||||
|
|
Loading…
Reference in New Issue