fix: public repo config and labels flicker (#2571)

* fix lint
* label fixes and minor ui fix
* udpate: label values api to use query params
* fix: public repo config and labels flicker
pull/3545/head
Ritik Kapoor 2024-08-28 08:14:43 +00:00 committed by Harness
parent e18a8c410f
commit 81b10ba01c
33 changed files with 560 additions and 421 deletions

View File

@ -72,6 +72,7 @@ export interface AppProps {
useLogsStreaming: Unknown
useFeatureFlags: Unknown
useGetSettingValue: Unknown
useGetAuthSettings: Unknown
useGetUserSourceCodeManagers?: Unknown
useListAggregatedTokens?: Unknown
useDeleteToken?: Unknown

View File

@ -21,6 +21,7 @@ import { routes } from 'RouteDefinitions'
import { defaultCurrentUser } from 'AppContext'
import { useFeatureFlags } from 'hooks/useFeatureFlag'
import { useGetSettingValue } from 'hooks/useGetSettingValue'
import { useGetAuthSettings } from 'hooks/useGetAuthSettings'
import { defaultUsefulOrNot } from 'components/DefaultUsefulOrNot/UsefulOrNot'
import App from './App'
import './bootstrap.scss'
@ -41,7 +42,8 @@ ReactDOM.render(
useLogsContent: noop,
useLogsStreaming: noop,
useFeatureFlags,
useGetSettingValue
useGetSettingValue,
useGetAuthSettings
}}
currentUser={defaultCurrentUser}
customComponents={{

View File

@ -93,6 +93,6 @@
}
.valuesList {
flex-wrap: wrap;
gap: 7px;
gap: 8px;
width: 100%;
}

View File

@ -19,7 +19,7 @@ import cx from 'classnames'
import { Button, ButtonSize, ButtonVariation, Container, Layout, Tag, Text } from '@harnessio/uicore'
import { FontVariation } from '@harnessio/design-system'
import { useGet } from 'restful-react'
import { Menu } from '@blueprintjs/core'
import { Menu, Spinner } from '@blueprintjs/core'
import { Icon } from '@harnessio/icons'
import { isEmpty } from 'lodash-es'
import { ColorName, LabelType, getColorsObj, getScopeData, getScopeIcon } from 'utils/Utils'
@ -329,7 +329,59 @@ export const LabelValuesList: React.FC<{
/>
))
) : (
<Icon name="steps-spinner" size={16} />
<Spinner size={16} />
)}
</Layout.Horizontal>
)
}
// ToDo : Remove LabelValuesListQuery component when Encoding is handled by BE for Harness
export const LabelValuesListQuery: React.FC<{
name: string
scope: number
repoMetadata?: RepoRepositoryOutput
space?: string
standalone: boolean
}> = ({ name, scope, repoMetadata, space = '', standalone }) => {
const { scopeRef } = getScopeData(space as string, scope, standalone)
const getPath = () =>
scope === 0
? `/repos/${repoMetadata?.identifier}/labels/${encodeURIComponent(name)}/values`
: `/labels/${encodeURIComponent(name)}/values`
const {
data: labelValues,
refetch: refetchLabelValues,
loading: loadingLabelValues
} = useGet<TypesLabelValue[]>({
base: getConfig('code/api/v1'),
path: getPath(),
queryParams: {
accountIdentifier: scopeRef?.split('/')[0],
orgIdentifier: scopeRef?.split('/')[1],
projectIdentifier: scopeRef?.split('/')[2]
},
lazy: true
})
useEffect(() => {
refetchLabelValues()
}, [name, scope, space, repoMetadata])
return (
<Layout.Horizontal className={css.valuesList}>
{!loadingLabelValues && labelValues && !isEmpty(labelValues) ? (
labelValues.map(value => (
<Label
key={`${name}-${value.value}`}
name={name}
scope={scope}
label_value={{ name: value.value, color: value.color as ColorName }}
/>
))
) : (
<Spinner size={16} />
)}
</Layout.Horizontal>
)

View File

@ -129,45 +129,47 @@ export const LabelFilter = (props: LabelFilterProps) => {
const getLabelValuesPromise = async (key: string, scope: number): Promise<SelectOption[]> => {
setLoadingLabelValues(true)
const { scopeRef } = getScopeData(spaceRef, scope, standalone)
if (scope === 0) {
try {
const fetchedValues: TypesLabelValue[] = await getUsingFetch(
getConfig('code/api/v1'),
`/repos/${encodeURIComponent(repoMetadata?.path as string)}/labels/${encodeURIComponent(key)}/values`,
bearerToken,
{}
)
const updatedValuesList = mapToSelectOptions(fetchedValues)
setLoadingLabelValues(false)
return updatedValuesList
} catch (error) {
setLoadingLabelValues(false)
showError(getErrorMessage(error))
return []
}
} else {
try {
const fetchedValues: TypesLabelValue[] = await getUsingFetch(
getConfig('code/api/v1'),
`/spaces/${encodeURIComponent(scopeRef)}/labels/${encodeURIComponent(key)}/values`,
bearerToken,
{}
)
const updatedValuesList = Array.isArray(fetchedValues)
? ([
...(fetchedValues || []).map(item => ({
label: JSON.stringify(item),
value: String(item?.id)
}))
] as SelectOption[])
: ([] as SelectOption[])
setLoadingLabelValues(false)
return updatedValuesList
} catch (error) {
setLoadingLabelValues(false)
showError(getErrorMessage(error))
return []
}
const getPath = () =>
scope === 0
? `/repos/${encodeURIComponent(repoMetadata?.path as string)}/labels/${encodeURIComponent(key)}/values`
: `/spaces/${encodeURIComponent(scopeRef)}/labels/${encodeURIComponent(key)}/values`
try {
const fetchedValues: TypesLabelValue[] = await getUsingFetch(getConfig('code/api/v1'), getPath(), bearerToken, {})
const updatedValuesList = mapToSelectOptions(fetchedValues)
setLoadingLabelValues(false)
return updatedValuesList
} catch (error) {
setLoadingLabelValues(false)
showError(getErrorMessage(error))
return []
}
}
// ToDo : Remove getLabelValuesPromiseQuery component when Encoding is handled by BE for Harness
const getLabelValuesPromiseQuery = async (key: string, scope: number): Promise<SelectOption[]> => {
setLoadingLabelValues(true)
const { scopeRef } = getScopeData(spaceRef, scope, standalone)
const getPath = () =>
scope === 0
? `/repos/${repoMetadata?.identifier}/labels/${encodeURIComponent(key)}/values`
: `/labels/${encodeURIComponent(key)}/values`
try {
const fetchedValues: TypesLabelValue[] = await getUsingFetch(getConfig('code/api/v1'), getPath(), bearerToken, {
queryParams: {
accountIdentifier: scopeRef?.split('/')[0],
orgIdentifier: scopeRef?.split('/')[1],
projectIdentifier: scopeRef?.split('/')[2]
}
})
const updatedValuesList = mapToSelectOptions(fetchedValues)
setLoadingLabelValues(false)
return updatedValuesList
} catch (error) {
setLoadingLabelValues(false)
showError(getErrorMessage(error))
return []
}
}
@ -320,11 +322,20 @@ export const LabelFilter = (props: LabelFilterProps) => {
setIsVisible(true)
setValueQuery('')
setHighlightItem(item.label as string)
getLabelValuesPromise(itemObj.key, itemObj.scope)
.then(res => setLabelValues(res))
.catch(err => {
showError(getErrorMessage(err))
})
// ToDo : Remove this check once BE has support for encoding
if (standalone) {
getLabelValuesPromise(itemObj.key, itemObj.scope)
.then(res => setLabelValues(res))
.catch(err => {
showError(getErrorMessage(err))
})
} else {
getLabelValuesPromiseQuery(itemObj.key, itemObj.scope)
.then(res => setLabelValues(res))
.catch(err => {
showError(getErrorMessage(err))
})
}
}}
tooltip={
labelsValueList && !loadingLabelValues ? (

View File

@ -56,6 +56,7 @@ export interface LabelSelectorProps {
setQuery: React.Dispatch<React.SetStateAction<string>>
query: string
labelListLoading: boolean
refetchActivities: () => void
}
export interface LabelSelectProps extends Omit<ButtonProps, 'onSelect'> {
@ -87,6 +88,7 @@ export const LabelSelector: React.FC<LabelSelectorProps> = ({
query,
setQuery,
labelListLoading,
refetchActivities,
...props
}) => {
const [popoverDialogOpen, setPopoverDialogOpen] = useState<boolean>(false)
@ -110,7 +112,7 @@ export const LabelSelector: React.FC<LabelSelectorProps> = ({
resourceType: 'CODE_REPOSITORY',
resourceIdentifier: repoMetadata?.identifier as string
},
permissions: ['code_repo_edit']
permissions: ['code_repo_push']
},
[space]
)
@ -121,6 +123,7 @@ export const LabelSelector: React.FC<LabelSelectorProps> = ({
text={<span className={css.prefix}>{getString('add')}</span>}
variation={ButtonVariation.TERTIARY}
minimal
disabled={labelListLoading}
size={ButtonSize.SMALL}
tooltip={
<PopoverContent
@ -139,6 +142,7 @@ export const LabelSelector: React.FC<LabelSelectorProps> = ({
setQuery('')
setPopoverDialogOpen(false)
showSuccess(`Applied '${label.key}' label`)
refetchActivities()
})
.catch(error => showError(getErrorMessage(error)))
} catch (exception) {
@ -161,6 +165,7 @@ export const LabelSelector: React.FC<LabelSelectorProps> = ({
setCurrentLabel({ key: '', id: -1 })
setPopoverDialogOpen(false)
showSuccess(`Applied '${labelKey}:${valueKey}' label`)
refetchActivities()
})
.catch(error => showError(getErrorMessage(error)))
} catch (exception) {
@ -185,6 +190,7 @@ export const LabelSelector: React.FC<LabelSelectorProps> = ({
setCurrentLabel({ key: '', id: -1 })
setPopoverDialogOpen(false)
setQuery('')
refetchActivities()
})
.catch(error => showError(getErrorMessage(error)))
} catch (exception) {
@ -266,60 +272,62 @@ const PopoverContent: React.FC<LabelSelectProps> = ({
}
}, [menuItemIndex, menuState, labelsList, labelsValueList])
const handleKeyDownLabels: React.KeyboardEventHandler<HTMLInputElement> = e => {
if (labelsList && labelsList.length !== 0) {
switch (e.key) {
case 'ArrowDown':
setMenuItemIndex((index: number) => {
return index + 1 > labelsList.length ? 1 : index + 1
})
break
case 'ArrowUp':
setMenuItemIndex((index: number) => {
return index - 1 > 0 ? index - 1 : labelsList.length
})
break
case 'Enter':
if (labelsList[menuItemIndex - 1]) {
onSelectLabel(labelsList[menuItemIndex - 1])
setQuery('')
}
break
default:
break
}
}
}
// const handleKeyDownLabels: React.KeyboardEventHandler<HTMLInputElement> = e => {
// if (labelsList && labelsList.length !== 0) {
// e.preventDefault()
// switch (e.key) {
// case 'ArrowDown':
// setMenuItemIndex((index: number) => {
// return index + 1 > labelsList.length ? 1 : index + 1
// })
// break
// case 'ArrowUp':
// setMenuItemIndex((index: number) => {
// return index - 1 > 0 ? index - 1 : labelsList.length
// })
// break
// case 'Enter':
// if (labelsList[menuItemIndex - 1]) {
// onSelectLabel(labelsList[menuItemIndex - 1])
// setQuery('')
// }
// break
// default:
// break
// }
// }
// }
const handleKeyDownValue: React.KeyboardEventHandler<HTMLInputElement> = e => {
if (e.key === 'Backspace' && !query && currentLabel) {
setQuery('')
handleValueRemove && handleValueRemove()
} else if (labelsValueList && labelsValueList.length !== 0) {
switch (e.key) {
case 'ArrowDown':
setMenuItemIndex((index: number) => {
return index + 1 > labelsValueList.length ? 1 : index + 1
})
break
case 'ArrowUp':
setMenuItemIndex((index: number) => {
return index - 1 > 0 ? index - 1 : labelsValueList.length
})
break
case 'Enter':
onSelectValue(
currentLabel.id ?? -1,
labelsValueList[menuItemIndex - 1].id ?? -1,
currentLabel.key ?? '',
labelsValueList[menuItemIndex - 1].value ?? ''
)
setQuery('')
break
default:
break
}
}
// } else if (labelsValueList && labelsValueList.length !== 0) {
// switch (e.key) {
// case 'ArrowDown':
// setMenuItemIndex((index: number) => {
// return index + 1 > labelsValueList.length ? 1 : index + 1
// })
// break
// case 'ArrowUp':
// setMenuItemIndex((index: number) => {
// return index - 1 > 0 ? index - 1 : labelsValueList.length
// })
// break
// case 'Enter':
// onSelectValue(
// currentLabel.id ?? -1,
// labelsValueList[menuItemIndex - 1].id ?? -1,
// currentLabel.key ?? '',
// labelsValueList[menuItemIndex - 1].value ?? ''
// )
// setQuery('')
// break
// default:
// break
// }
// }
}
return (
@ -343,7 +351,7 @@ const PopoverContent: React.FC<LabelSelectProps> = ({
className: css.closeBtn,
size: 20
}}
onKeyDown={handleKeyDownLabels}
// onKeyDown={handleKeyDownLabels}
/>
) : (
currentLabel &&

View File

@ -76,6 +76,7 @@ import type {
} from 'services/code'
import { useAppContext } from 'AppContext'
import type { TypesRepository } from 'cde-gitness/services'
import { usePublicResourceConfig } from 'hooks/usePublicResourceConfig'
import ImportForm from './ImportForm/ImportForm'
import ImportReposForm from './ImportReposForm/ImportReposForm'
import Private from '../../icons/private.svg?url'
@ -112,7 +113,8 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
const ModalComponent: React.FC = () => {
const { getString } = useStrings()
const [branchName, setBranchName] = useState(DEFAULT_BRANCH_NAME)
const [enablePublicRepo, setEnablePublicRepo] = useState(false)
const { allowPublicResourceCreation, configLoading, systemConfigError, errorWhileFetchingAuthSettings } =
usePublicResourceConfig()
const { showError } = useToaster()
const { mutate: createRepo, loading: submitLoading } = useMutate<RepoRepositoryOutput>({
@ -147,31 +149,19 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
loading: licenseLoading,
error: licenseError
} = useGet({ path: '/api/v1/resources/license' })
const {
data: systemConfig,
loading: systemConfigLoading,
error: systemConfigError
} = useGet({ path: 'api/v1/system/config' })
const loading =
submitLoading ||
gitIgnoreLoading ||
licenseLoading ||
importRepoLoading ||
submitImportLoading ||
systemConfigLoading
submitLoading || gitIgnoreLoading || licenseLoading || importRepoLoading || submitImportLoading || configLoading
useEffect(() => {
if (gitIgnoreError || licenseError || systemConfigError) {
showError(getErrorMessage(gitIgnoreError || licenseError || systemConfigError), 0)
if (gitIgnoreError || licenseError || systemConfigError || errorWhileFetchingAuthSettings) {
showError(
getErrorMessage(gitIgnoreError || licenseError || systemConfigError || errorWhileFetchingAuthSettings),
0
)
}
}, [gitIgnoreError, licenseError, systemConfigError, showError])
}, [gitIgnoreError, licenseError, systemConfigError, errorWhileFetchingAuthSettings, showError])
useEffect(() => {
if (systemConfig) {
setEnablePublicRepo(systemConfig.public_resource_creation_enabled)
}
}, [systemConfig])
const handleSubmit = (formData: RepoFormData) => {
try {
const payload: OpenapiCreateRepositoryRequest = {
@ -354,7 +344,7 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
{getString('createRepoModal.branch')}
</Text>
</Container>
<Render when={enablePublicRepo}>
<Render when={allowPublicResourceCreation}>
<hr className={css.dividerContainer} />
<Container>
<FormInput.RadioGroup

View File

@ -651,8 +651,11 @@ export interface StringsMap {
'labels.createdIn': string
'labels.deleteLabel': string
'labels.deleteLabelConfirm': string
'labels.deletedLabel': string
'labels.descriptionOptional': string
'labels.failedToDeleteLabel': string
'labels.failedtoFetchLabels': string
'labels.failedtoFetchValues': string
'labels.filterByLabels': string
'labels.findALabel': string
'labels.findOrAdd': string

View File

@ -0,0 +1,19 @@
/*
* 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.
*/
export function useGetAuthSettings<T = Record<string, boolean>>() {
return {} as T
}

View File

@ -0,0 +1,51 @@
/*
* 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 { useEffect, useMemo } from 'react'
import { useGet } from 'restful-react'
import { useAppContext } from 'AppContext'
export function usePublicResourceConfig() {
const { standalone, hooks } = useAppContext()
const { refetchAuthSettings, authSettings, fetchingAuthSettings, errorWhileFetchingAuthSettings } =
hooks.useGetAuthSettings()
const {
data: systemConfig,
loading: systemConfigLoading,
error: systemConfigError
} = useGet({ path: 'api/v1/system/config' })
useEffect(() => {
if (!standalone) refetchAuthSettings()
}, [refetchAuthSettings])
const allowPublicResourceCreation = useMemo(() => {
if (systemConfigLoading || fetchingAuthSettings) {
return false
}
if (standalone) {
return systemConfig?.public_resource_creation_enabled
}
return !!(systemConfig?.public_resource_creation_enabled && authSettings?.resource?.publicAccessEnabled)
}, [authSettings, systemConfig, standalone, systemConfigLoading, fetchingAuthSettings])
return {
allowPublicResourceCreation,
configLoading: fetchingAuthSettings || systemConfigLoading,
systemConfigError,
errorWhileFetchingAuthSettings
}
}

View File

@ -128,9 +128,9 @@ repos:
createRepoModal:
branchLabel: 'Your repository will be initialized with a '
branch: ' branch.'
publicLabel: Anyone with access to the Gitness environment can clone this repo.
publicLabel: Anyone who can view the repository can clone it.
privateLabel: You choose who can see and commit to this repository.
publicWarning: Please note that anyone with access to the Gitness environment can clone this repo.
publicWarning: Please note that anyone who can view the repository can clone it.
validation:
repoNamePatternIsNotValid: Registry name should be 1~255 characters long with lower case characters, numbers and ._- and must be start with numbers or characters
gitBranchNameInvalid: Branch name is invalid.
@ -1315,6 +1315,7 @@ labels:
addValue: 'Add value'
removeLabel: Remove Label
deleteLabel: Delete Label
deletedLabel: 'Deleted Label : {tag}'
provideLabelName: Provide Label name
labelCreated: Label created
labelUpdated: Label updated
@ -1347,6 +1348,8 @@ labels:
deleteLabelConfirm: Are you sure you want to delete label <strong>{{name}}</strong>? You can't undo this action.
intentText: Editing/deleting a label or its values will impact all the areas it has been used.
prCount: 'Showing {count} {count|1:result,results}'
failedtoFetchLabels: Failed to fetch labels
failedtoFetchValues: Failed to fetch label values
noResults: No results found
labelPreview: Label Preview
filterByLabels: Filter by Label/s

View File

@ -33,7 +33,7 @@ import {
} from '@harnessio/uicore'
import { Icon } from '@harnessio/icons'
import { Color, FontVariation } from '@harnessio/design-system'
import { Menu, MenuItem, PopoverInteractionKind, Position } from '@blueprintjs/core'
import { Menu, MenuItem, PopoverInteractionKind, Position, Spinner } from '@blueprintjs/core'
import * as Yup from 'yup'
import { FieldArray } from 'formik'
import { useGet, useMutate } from 'restful-react'
@ -181,30 +181,51 @@ const useLabelModal = ({ refetchlabelsList }: LabelModalProps) => {
path: updateLabel?.scope ? `/spaces/${scopeRef as string}/+/labels` : `/spaces/${space as string}/+/labels`
})
const getPath = () =>
updateLabel?.scope === 0 && repoMetadata
? `/repos/${encodeURIComponent(repoMetadata?.path as string)}/labels/${encodeURIComponent(
updateLabel?.key ? updateLabel?.key : ''
)}/values`
: `/spaces/${encodeURIComponent(scopeRef)}/labels/${encodeURIComponent(
updateLabel?.key ? updateLabel?.key : ''
)}/values`
const {
data: repoLabelValues,
loading: repoValueListLoading,
refetch: refetchRepoValuesList
data: initLabelValues,
loading: initValueListLoading,
refetch: refetchInitValuesList
} = useGet<TypesLabelValue[]>({
base: getConfig('code/api/v1'),
path: `/repos/${encodeURIComponent(repoMetadata?.path as string)}/labels/${encodeURIComponent(
updateLabel?.key ? updateLabel?.key : ''
)}/values`,
path: getPath(),
lazy: true
})
//ToDo : Remove getLabelValuesPromiseQuery component when Encoding is handled by BE for Harness
const getPathHarness = () =>
updateLabel?.scope === 0 && repoMetadata
? `/repos/${repoMetadata?.identifier}/labels/${encodeURIComponent(
updateLabel?.key ? updateLabel?.key : ''
)}/values`
: `/labels/${encodeURIComponent(updateLabel?.key ? updateLabel?.key : '')}/values`
const {
data: spaceLabelValues,
loading: spaceValueListLoading,
refetch: refetchSpaceValuesList
data: initLabelValuesQuery,
loading: initValueListLoadingQuery,
refetch: refetchInitValuesListQuery
} = useGet<TypesLabelValue[]>({
base: getConfig('code/api/v1'),
path: `/spaces/${encodeURIComponent(scopeRef)}/labels/${encodeURIComponent(
updateLabel?.key ? updateLabel?.key : ''
)}/values`,
path: getPathHarness(),
queryParams: {
accountIdentifier: scopeRef?.split('/')[0],
orgIdentifier: scopeRef?.split('/')[1],
projectIdentifier: scopeRef?.split('/')[2]
},
lazy: true
})
//ToDo: Remove all references of suffix with Query when Encoding is handled by BE for Harness
const [openModal, hideModal] = useModalHook(() => {
const handleLabelSubmit = (formData: LabelFormData) => {
const { labelName, color, labelValues, description, allowDynamicValues, id } = formData
@ -329,20 +350,20 @@ const useLabelModal = ({ refetchlabelsList }: LabelModalProps) => {
return { ...baseValues, color: ColorName.Blue }
}
if (repoMetadata && updateLabel?.scope === 0) {
return { ...baseValues, labelValues: getLabelValues(repoLabelValues ?? undefined) }
}
if (modalMode === ModalMode.UPDATE && updateLabel?.value_count === 0) return baseValues
return { ...baseValues, labelValues: getLabelValues(spaceLabelValues ?? undefined) }
if (standalone) {
return { ...baseValues, labelValues: getLabelValues(initLabelValues ?? undefined) }
}
return { ...baseValues, labelValues: getLabelValues(initLabelValuesQuery ?? undefined) }
})()
return (
<Dialog
isOpen
onOpening={() => {
if (modalMode === ModalMode.UPDATE) {
if (repoMetadata && updateLabel?.scope === 0) refetchRepoValuesList()
else refetchSpaceValuesList()
if (modalMode === ModalMode.UPDATE && updateLabel?.value_count !== 0) {
standalone ? refetchInitValuesList() : refetchInitValuesListQuery()
}
}}
enforceFocus={false}
@ -408,68 +429,75 @@ const useLabelModal = ({ refetchlabelsList }: LabelModalProps) => {
<FieldArray
name="labelValues"
render={({ push, remove }) => {
return (
<Layout.Vertical>
{formik.values.labelValues?.map((_, index) => (
<Layout.Horizontal
key={`labelValue + ${index}`}
flex={{
alignItems: formik.isValid ? 'center' : 'flex-start',
justifyContent: 'flex-start'
}}
style={{ gap: '4px', margin: '4px' }}>
<ColorSelectorDropdown
key={`labelValueColor + ${index}`}
currentColorName={
formik.values.labelValues &&
index !== undefined &&
(formik.values.labelValues[index].color as ColorName)
}
onClick={(colorName: ColorName) => {
formik.setFieldValue(
'labelValues',
formik.values.labelValues?.map((value, i) =>
i === index ? { ...value, color: colorName } : value
)
)
}}
/>
<FormInput.Text
key={`labelValueKey + ${index}`}
style={{ flexGrow: '1', margin: 0 }}
name={`${'labelValues'}[${index}].value`}
placeholder={getString('labels.provideLabelValue')}
tooltipProps={{
dataTooltipId: 'labels.newLabel'
}}
inputGroup={{ autoFocus: true }}
/>
<Button
key={`removeValue + ${index}`}
style={{ marginRight: 'auto', color: 'var(--grey-300)' }}
variation={ButtonVariation.ICON}
icon={'code-close'}
onClick={() => {
remove(index)
}}
/>
</Layout.Horizontal>
))}
<Button
style={{ marginRight: 'auto' }}
variation={ButtonVariation.LINK}
disabled={!formik.isValid || formik.values.labelName?.length === 0}
text={getString('labels.addValue')}
icon={CodeIcon.Add}
onClick={() =>
push({
name: '',
color: formik.values.color
})
}
/>
</Layout.Vertical>
if (
modalMode === ModalMode.UPDATE &&
updateLabel?.value_count !== 0 &&
(initValueListLoading || initValueListLoadingQuery)
)
return <Spinner size={20} />
else
return (
<Layout.Vertical>
{formik.values.labelValues?.map((_, index) => (
<Layout.Horizontal
key={`labelValue + ${index}`}
flex={{
alignItems: formik.isValid ? 'center' : 'flex-start',
justifyContent: 'flex-start'
}}
style={{ gap: '4px', margin: '4px' }}>
<ColorSelectorDropdown
key={`labelValueColor + ${index}`}
currentColorName={
formik.values.labelValues &&
index !== undefined &&
(formik.values.labelValues[index].color as ColorName)
}
onClick={(colorName: ColorName) => {
formik.setFieldValue(
'labelValues',
formik.values.labelValues?.map((value, i) =>
i === index ? { ...value, color: colorName } : value
)
)
}}
/>
<FormInput.Text
key={`labelValueKey + ${index}`}
style={{ flexGrow: '1', margin: 0 }}
name={`${'labelValues'}[${index}].value`}
placeholder={getString('labels.provideLabelValue')}
tooltipProps={{
dataTooltipId: 'labels.newLabel'
}}
inputGroup={{ autoFocus: true }}
/>
<Button
key={`removeValue + ${index}`}
style={{ marginRight: 'auto', color: 'var(--grey-300)' }}
variation={ButtonVariation.ICON}
icon={'code-close'}
onClick={() => {
remove(index)
}}
/>
</Layout.Horizontal>
))}
<Button
style={{ marginRight: 'auto' }}
variation={ButtonVariation.LINK}
disabled={!formik.isValid || formik.values.labelName?.length === 0}
text={getString('labels.addValue')}
icon={CodeIcon.Add}
onClick={() =>
push({
name: '',
color: formik.values.color
})
}
/>
</Layout.Vertical>
)
}}
/>
</Container>
@ -498,37 +526,43 @@ const useLabelModal = ({ refetchlabelsList }: LabelModalProps) => {
<Layout.Vertical
style={{ width: '45%', padding: '25px 35px 25px 35px', borderLeft: '1px solid var(--grey-100)' }}>
<Text>{getString('labels.labelPreview')}</Text>
<Layout.Vertical spacing={'medium'}>
{formik.values.labelValues?.length
? formik.values.labelValues?.map((valueObj, i) => (
<Label
key={`label + ${i}`}
name={formik.values.labelName || getString('labels.labelName')}
label_color={formik.values.color}
label_value={
valueObj.value?.length
? { name: valueObj.value, color: valueObj.color }
: {
name: getString('labels.labelValue'),
color: valueObj.color || formik.values.color
}
}
/>
))
: !formik.values.allowDynamicValues && (
<Label
name={formik.values.labelName || getString('labels.labelName')}
label_color={formik.values.color}
/>
)}
{formik.values.allowDynamicValues && (
<Label
name={formik.values.labelName || getString('labels.labelName')}
label_color={formik.values.color}
label_value={{ name: getString('labels.canbeAddedByUsers') }}
/>
)}
</Layout.Vertical>
{modalMode === ModalMode.UPDATE &&
updateLabel?.value_count !== 0 &&
(initValueListLoading || initValueListLoadingQuery) ? (
<Spinner size={20} />
) : (
<Layout.Vertical spacing={'medium'}>
{formik.values.labelValues?.length
? formik.values.labelValues?.map((valueObj, i) => (
<Label
key={`label + ${i}`}
name={formik.values.labelName || getString('labels.labelName')}
label_color={formik.values.color}
label_value={
valueObj.value?.length
? { name: valueObj.value, color: valueObj.color }
: {
name: getString('labels.labelValue'),
color: valueObj.color || formik.values.color
}
}
/>
))
: !formik.values.allowDynamicValues && (
<Label
name={formik.values.labelName || getString('labels.labelName')}
label_color={formik.values.color}
/>
)}
{formik.values.allowDynamicValues && (
<Label
name={formik.values.labelName || getString('labels.labelName')}
label_color={formik.values.color}
label_value={{ name: getString('labels.canbeAddedByUsers') }}
/>
)}
</Layout.Vertical>
)}
</Layout.Vertical>
</Layout.Horizontal>
</FormikForm>
@ -539,11 +573,12 @@ const useLabelModal = ({ refetchlabelsList }: LabelModalProps) => {
)
}, [
updateLabel,
repoLabelValues,
spaceLabelValues,
repoValueListLoading,
spaceValueListLoading,
initLabelValues,
initLabelValuesQuery,
initValueListLoading,
initValueListLoadingQuery,
refetchlabelsList,
refetchInitValuesListQuery,
modalMode
])

View File

@ -16,16 +16,7 @@
.main {
background-color: var(--primary-bg) !important;
.table {
.row {
height: 80px;
display: flex;
justify-content: center;
.title {
font-weight: 600;
}
}
}
.noData > div {
height: calc(100vh - var(--page-header-height, 64px) - 120px) !important;
}

View File

@ -19,10 +19,7 @@
export declare const cancelButton: string
export declare const main: string
export declare const noData: string
export declare const row: string
export declare const scopeCheckbox: string
export declare const table: string
export declare const title: string
export declare const toggle: string
export declare const toggleDisable: string
export declare const toggleEnable: string

View File

@ -18,9 +18,8 @@ import { Container, Layout, FlexExpander, ButtonVariation, Button, Checkbox } fr
import { Render } from 'react-jsx-match'
import { useStrings } from 'framework/strings'
import { CodeIcon } from 'utils/GitUtils'
import { useAppContext } from 'AppContext'
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
import { LabelsPageScope, permissionProps } from 'utils/Utils'
import type { LabelsPageScope } from 'utils/Utils'
import type { RepoRepositoryOutput } from 'services/code'
import css from './LabelsHeader.module.scss'
@ -30,28 +29,10 @@ const LabelsHeader = ({
showParentScopeFilter,
inheritLabels,
setInheritLabels,
openLabelCreateModal,
spaceRef,
repoMetadata,
currentPageScope
openLabelCreateModal
}: LabelsHeaderProps) => {
const [searchTerm, setSearchTerm] = useState('')
const { getString } = useStrings()
const { hooks, standalone } = useAppContext()
const permPushResult = hooks?.usePermissionTranslate?.(
{
resource: {
resourceType: 'CODE_REPOSITORY',
resourceIdentifier:
currentPageScope === LabelsPageScope.REPOSITORY && repoMetadata
? repoMetadata.identifier
: (spaceRef as string)
},
permissions: ['code_repo_edit']
},
[spaceRef]
)
//ToDo: check space permissions as well in case of spaces
@ -63,7 +44,6 @@ const LabelsHeader = ({
text={getString('labels.newLabel')}
icon={CodeIcon.Add}
onClick={openLabelCreateModal}
{...permissionProps(permPushResult, standalone)}
/>
<Render when={showParentScopeFilter}>
<Checkbox

View File

@ -29,60 +29,16 @@
max-width: 55%;
}
div[class*='TableV2--row'] {
padding: 3px var(--spacing-medium);
justify-content: center;
min-height: 48px;
}
div[class*='TableV2--rowSubComponent'] {
border-top: 1px solid var(--grey-100);
padding: 20px 4px 4px 5%;
}
}
.dividerContainer {
opacity: 0.2;
height: 1px;
color: var(--grey-100);
margin: 10px 0;
width: 95%;
}
.border {
border-bottom: 1px solid var(--grey-100) !important;
}
.hideDetailsContainer {
display: none;
}
.appliedRulesTextContainer {
border-radius: 4px;
background: var(--grey-50) !important;
font-size: 12px !important;
font-weight: 500 !important;
padding: var(--spacing-small) !important;
margin-bottom: var(--spacing-xsmall) !important;
}
.popover {
z-index: 999;
padding: var(--spacing-tiny) !important;
}
.widthContainer {
max-width: calc(100% - 100px);
overflow-x: hidden;
display: flex;
flex-wrap: wrap;
}
.hideButtonIcon {
:global {
[class*='ConfirmationDialog--header'] {
.bp3-icon {
display: none !important;
}
}
[class*='ConfirmationDialog--body'] {
padding-left: 3px !important;
}
padding: 14px var(--spacing-xsmall) 14px 4.7%;
margin-top: var(--spacing-tiny);
}
}
}

View File

@ -16,16 +16,9 @@
/* eslint-disable */
// This is an auto-generated file
export declare const appliedRulesTextContainer: string
export declare const border: string
export declare const dividerContainer: string
export declare const hideButtonIcon: string
export declare const hideDetailsContainer: string
export declare const labelCtn: string
export declare const main: string
export declare const optionItem: string
export declare const popover: string
export declare const row: string
export declare const table: string
export declare const toggleAccordion: string
export declare const widthContainer: string

View File

@ -38,7 +38,6 @@ import { usePageIndex } from 'hooks/usePageIndex'
import {
getErrorMessage,
LIST_FETCHING_LIMIT,
permissionProps,
type PageBrowserProps,
ColorName,
LabelTypes,
@ -54,7 +53,7 @@ import { useConfirmAction } from 'hooks/useConfirmAction'
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
import { useAppContext } from 'AppContext'
import { useUpdateQueryParams } from 'hooks/useUpdateQueryParams'
import { LabelTitle, LabelValuesList } from 'components/Label/Label'
import { LabelTitle, LabelValuesList, LabelValuesListQuery } from 'components/Label/Label'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { getConfig } from 'services/config'
import LabelsHeader from './LabelsHeader/LabelsHeader'
@ -63,7 +62,7 @@ import css from './LabelsListing.module.scss'
const LabelsListing = (props: LabelListingProps) => {
const { activeTab, currentPageScope, repoMetadata, space } = props
const { hooks, standalone } = useAppContext()
const { standalone } = useAppContext()
const { getString } = useStrings()
const { showError, showSuccess } = useToaster()
const history = useHistory()
@ -133,15 +132,27 @@ const LabelsListing = (props: LabelListingProps) => {
const { openModal: openLabelCreateModal, openUpdateLabelModal } = useLabelModal({ refetchlabelsList })
const renderRowSubComponent = React.useCallback(({ row }: { row: Row<LabelTypes> }) => {
return (
<LabelValuesList
name={row.original?.key as string}
scope={row.original?.scope as number}
repoMetadata={repoMetadata}
space={space}
standalone={standalone}
/>
)
if (standalone) {
return (
<LabelValuesList
name={row.original?.key as string}
scope={row.original?.scope as number}
repoMetadata={repoMetadata}
space={space}
standalone={standalone}
/>
)
} else {
return (
<LabelValuesListQuery
name={row.original?.key as string}
scope={row.original?.scope as number}
repoMetadata={repoMetadata}
space={space}
standalone={standalone}
/>
)
}
}, [])
const ToggleAccordionCell: Renderer<{
@ -215,7 +226,7 @@ const LabelsListing = (props: LabelListingProps) => {
width: '40%',
sort: 'true',
Cell: ({ row }: CellProps<LabelTypes>) => {
return <Text lineClamp={3}>{row.original?.description}</Text>
return <Text lineClamp={1}>{row.original?.description}</Text>
}
},
{
@ -235,6 +246,24 @@ const LabelsListing = (props: LabelListingProps) => {
path: deleteLabelPath
})
//ToDo : Remove the following block when Encoding is handled by BE for Harness
const deleteLabelPathHarness =
row.original?.scope === 0
? `/repos/${repoMetadata?.identifier}/labels/${encodedLabelKey}`
: `/labels/${encodedLabelKey}`
const { mutate: deleteLabelQueryCall } = useMutate({
verb: 'DELETE',
base: getConfig('code/api/v1'),
path: deleteLabelPathHarness,
queryParams: {
accountIdentifier: scopeRef?.split('/')[0],
orgIdentifier: scopeRef?.split('/')[1],
projectIdentifier: scopeRef?.split('/')[2]
}
})
//ToDo: remove type check of standalone when Encoding is handled by BE for Harness
const confirmLabelDelete = useConfirmAction({
title: getString('labels.deleteLabel'),
confirmText: getString('delete'),
@ -242,23 +271,19 @@ const LabelsListing = (props: LabelListingProps) => {
message: <String useRichText stringID="labels.deleteLabelConfirm" vars={{ name: row.original.key }} />,
action: async e => {
e.stopPropagation()
deleteLabel({})
.then(() => {
showSuccess(
<StringSubstitute
str={getString('labels.deleteLabel')}
vars={{
tag: row.original.key
}}
/>,
5000
)
refetchlabelsList()
setPage(1)
})
.catch(error => {
showError(getErrorMessage(error), 0, getString('labels.failedToDeleteLabel'))
})
const handleSuccess = (tag: string) => {
showSuccess(<StringSubstitute str={getString('labels.deletedLabel')} vars={{ tag }} />, 5000)
refetchlabelsList()
setPage(1)
}
const handleError = (error: any) => {
showError(getErrorMessage(error), 0, getString('labels.failedToDeleteLabel'))
}
const deleteAction = standalone ? deleteLabel({}) : deleteLabelQueryCall({})
deleteAction.then(() => handleSuccess(row.original.key ?? '')).catch(handleError)
}
})
return (
@ -296,18 +321,6 @@ const LabelsListing = (props: LabelListingProps) => {
[history, getString, repoMetadata?.path, space, setPage, showError, showSuccess]
)
const permPushResult = hooks?.usePermissionTranslate?.(
{
resource: {
resourceType: 'CODE_REPOSITORY',
resourceIdentifier:
currentPageScope === LabelsPageScope.REPOSITORY ? (repoMetadata?.identifier as string) : (space as string)
},
permissions: ['code_repo_edit']
},
[space]
)
return (
<Container>
<LabelsHeader
@ -346,7 +359,6 @@ const LabelsListing = (props: LabelListingProps) => {
message={getString('labels.noLabelsFound')}
buttonText={getString('labels.newLabel')}
onButtonClick={() => openLabelCreateModal()}
permissionProp={permissionProps(permPushResult, standalone)}
/>
</Container>
)

View File

@ -51,6 +51,7 @@
border-radius: 8px;
box-shadow: 0px 0.5px 2px 0px rgba(96, 97, 112, 0.16), 0px 0px 1px 0px rgba(40, 41, 61, 0.08);
background: var(--grey-0);
cursor: pointer;
&.expanded {
.chevron {

View File

@ -81,6 +81,8 @@ export interface ConversationProps extends Pick<GitInfoProps, 'repoMetadata' | '
standalone: boolean
routingId: string
pullReqCommits: TypesListCommitResponse | undefined
refetchActivities: () => void
refetchPullReq: () => void
}
export const Conversation: React.FC<ConversationProps> = ({
@ -93,7 +95,9 @@ export const Conversation: React.FC<ConversationProps> = ({
prChecksDecisionResult,
standalone,
routingId,
pullReqCommits
pullReqCommits,
refetchActivities,
refetchPullReq
}) => {
const { getString } = useStrings()
const { currentUser, routes, hooks } = useAppContext()
@ -442,6 +446,7 @@ export const Conversation: React.FC<ConversationProps> = ({
setActivityFilter={setActivityFilter}
loadingReviewers={loadingReviewers}
refetchCodeOwners={refetchCodeOwners}
refetchPullReq={refetchPullReq}
activities={activities}
/>
</Container>
@ -508,6 +513,7 @@ export const Conversation: React.FC<ConversationProps> = ({
refetchReviewers={refetchReviewers}
labels={labels}
refetchLabels={refetchLabels}
refetchActivities={refetchActivities}
/>
</Layout.Horizontal>
</Container>

View File

@ -35,7 +35,11 @@ import { useAppContext } from 'AppContext'
import type { ConversationProps } from './Conversation'
import css from './Conversation.module.scss'
interface DescriptionBoxProps extends Omit<ConversationProps, 'onCancelEditDescription' | 'pullReqCommits'> {
interface DescriptionBoxProps
extends Omit<
ConversationProps,
'onCancelEditDescription' | 'pullReqCommits' | 'refetchActivities' | 'refetchPullReq'
> {
onCancelEditDescription: () => void
pullReqCommits: TypesListCommitResponse | undefined
}

View File

@ -65,7 +65,8 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
allowedStrategy,
pullReqCommits,
PRStateLoading,
setConflictingFiles
setConflictingFiles,
refetchPullReq
}) => {
const { getString } = useStrings()
const { showError } = useToaster()
@ -127,7 +128,8 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
setAllowedStrats,
pullRequestSection,
showError,
setConflictingFiles
setConflictingFiles,
refetchPullReq
) // eslint-disable-next-line react-hooks/exhaustive-deps
}, [unchecked, pullReqMetadata?.source_sha])
const [prMerged, setPrMerged] = useState(false)
@ -146,7 +148,8 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
setAllowedStrats,
pullRequestSection,
showError,
setConflictingFiles
setConflictingFiles,
refetchPullReq
)
}
}, POLLING_INTERVAL) // Poll every 20 seconds
@ -154,7 +157,7 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
return () => {
clearInterval(intervalId)
} // eslint-disable-next-line react-hooks/exhaustive-deps
}, [onPRStateChanged, prMerged])
}, [onPRStateChanged, prMerged, pullReqMetadata?.source_sha])
const isDraft = pullReqMetadata.is_draft
const mergeOptions: PRMergeOption[] = [
{

View File

@ -51,6 +51,7 @@ interface PullRequestOverviewPanelProps {
setActivityFilter: (val: SelectOption) => void
loadingReviewers: boolean
refetchCodeOwners: () => void
refetchPullReq: () => void
activities: TypesPullReqActivity[] | undefined
pullReqCommits: TypesListCommitResponse | undefined
}
@ -67,7 +68,8 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => {
loadingReviewers,
refetchCodeOwners,
activities,
pullReqCommits
pullReqCommits,
refetchPullReq
} = props
const { getString } = useStrings()
const { showError } = useToaster()
@ -174,6 +176,7 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => {
pullRequestSection,
showError,
setConflictingFiles,
refetchPullReq,
setRequiresCommentApproval,
setAtLeastOneReviewerRule,
setReqCodeOwnerApproval,
@ -197,6 +200,7 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => {
allowedStrategy={allowedStrats}
pullReqCommits={pullReqCommits}
PRStateLoading={PRStateLoading || loadingReviewers}
refetchPullReq={refetchPullReq}
/>
{pullReqMetadata.state !== PullRequestState.CLOSED && (
<PullRequestPanelSections

View File

@ -132,7 +132,7 @@ const MergeSection = (props: MergeSectionProps) => {
</Layout.Vertical>
)}
</Layout.Horizontal>
{!mergeable && (
{!mergeable && !unchecked && (
<Button
padding={{ right: 'unset' }}
className={cx(css.blueText, css.buttonPadding)}

View File

@ -15,7 +15,7 @@
*/
import React, { useState } from 'react'
import { PopoverInteractionKind } from '@blueprintjs/core'
import { PopoverInteractionKind, Spinner } from '@blueprintjs/core'
import { useGet, useMutate } from 'restful-react'
import { omit } from 'lodash-es'
import cx from 'classnames'
@ -43,13 +43,15 @@ interface PullRequestSideBarProps {
pullRequestMetadata: TypesPullReq
refetchReviewers: () => void
refetchLabels: () => void
refetchActivities: () => void
}
const PullRequestSideBar = (props: PullRequestSideBarProps) => {
const { standalone, hooks } = useAppContext()
const { CODE_PULLREQ_LABELS: isLabelEnabled } = hooks?.useFeatureFlags()
const [labelQuery, setLabelQuery] = useState<string>('')
const { reviewers, repoMetadata, pullRequestMetadata, refetchReviewers, labels, refetchLabels } = props
const { reviewers, repoMetadata, pullRequestMetadata, refetchReviewers, labels, refetchLabels, refetchActivities } =
props
const { getString } = useStrings()
const { showError, showSuccess } = useToaster()
const generateReviewDecisionInfo = (
@ -166,13 +168,10 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
path: ({ id }) => `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata?.number}/reviewers/${id}`
})
const { mutate: removeLabel } = useMutate({
const { mutate: removeLabel, loading: removingLabel } = useMutate({
verb: 'DELETE',
base: getConfig('code/api/v1'),
path: ({ label_id }) =>
`/repos/${encodeURIComponent(repoMetadata.path as string)}/pullreq/${
pullRequestMetadata?.number
}/labels/${encodeURIComponent(label_id)}`
path: ({ label_id }) => `/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata?.number}/labels/${label_id}`
})
const {
@ -420,6 +419,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
query={labelQuery}
setQuery={setLabelQuery}
labelListLoading={labelListLoading}
refetchActivities={refetchActivities}
/>
</Layout.Horizontal>
<Container padding={{ top: 'medium', bottom: 'large' }}>
@ -451,6 +451,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
label: label.key
}) as string
)
refetchActivities()
})
.catch(err => {
showError(getErrorMessage(err))
@ -464,6 +465,7 @@ const PullRequestSideBar = (props: PullRequestSideBarProps) => {
{getString('labels.noLabels')}
</Text>
)}
{removingLabel && <Spinner size={16} />}
</Layout.Horizontal>
</Container>
</Layout.Vertical>

View File

@ -59,6 +59,7 @@ export default function PullRequest() {
commitSHA,
refetchActivities,
refetchCommits,
refetchPullReq,
retryOnErrorFunc
} = useGetPullRequestInfo()
@ -158,6 +159,8 @@ export default function PullRequest() {
prStats={pullReqStats}
showEditDescription={showEditDescription}
onCancelEditDescription={() => setShowEditDescription(false)}
refetchPullReq={refetchPullReq}
refetchActivities={refetchActivities}
/>
)
},

View File

@ -207,6 +207,7 @@ export function useGetPullRequestInfo() {
commitSHA,
refetchActivities,
refetchCommits,
refetchPullReq,
retryOnErrorFunc
}
}

View File

@ -274,8 +274,7 @@ export default function PullRequests() {
(isLabelEnabled || standalone) &&
row.original &&
row.original.labels &&
row.original.labels.length !== 0 &&
!prLoading
row.original.labels.length !== 0
}>
{row.original?.labels?.map((label, index) => (
<Label
@ -406,19 +405,14 @@ export default function PullRequests() {
<Layout.Horizontal
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}
style={{ flexWrap: 'wrap', gap: '5px' }}>
<Render when={!prLoading}>
{isEmpty(data) ? (
<Text color={Color.GREY_400}>{getString('labels.noResults')}</Text>
) : (
<Text color={Color.GREY_400}>
{
stringSubstitute(getString('labels.prCount'), {
count: data?.length
}) as string
}
</Text>
)}
</Render>
<Text color={Color.GREY_400}>
{isEmpty(data)
? getString('labels.noResults')
: (stringSubstitute(getString('labels.prCount'), {
count: data?.length
}) as string)}
</Text>
{(isLabelEnabled || standalone) &&
labelFilter &&
labelFilter?.length !== 0 &&

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { useState, useEffect } from 'react'
import React, { useState } from 'react'
import {
Container,
Layout,
@ -32,7 +32,7 @@ import cx from 'classnames'
import { Color, FontVariation, Intent } from '@harnessio/design-system'
import { Icon } from '@harnessio/icons'
import { noop } from 'lodash-es'
import { useMutate, useGet } from 'restful-react'
import { useMutate } from 'restful-react'
import { Render } from 'react-jsx-match'
import { ACCESS_MODES, getErrorMessage, permissionProps, voidFn } from 'utils/Utils'
import { useStrings } from 'framework/strings'
@ -42,6 +42,7 @@ import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import { RepoVisibility } from 'utils/GitUtils'
import { BranchTagSelect } from 'components/BranchTagSelect/BranchTagSelect'
import { useModalHook } from 'hooks/useModalHook'
import { usePublicResourceConfig } from 'hooks/usePublicResourceConfig'
import useDeleteRepoModal from './DeleteRepoModal/DeleteRepoModal'
import useDefaultBranchModal from './DefaultBranchModal/DefaultBranchModal'
import Private from '../../../icons/private.svg?url'
@ -62,14 +63,13 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
const [defaultBranch, setDefaultBranch] = useState(ACCESS_MODES.VIEW)
const { openModal: openDefaultBranchModal } = useDefaultBranchModal({ currentGitRef, setDefaultBranch, refetch })
const { showError, showSuccess } = useToaster()
const { standalone, hooks } = useAppContext()
const space = useGetSpaceParam()
const { standalone, hooks, isPublicAccessEnabledOnResources } = useAppContext()
const { allowPublicResourceCreation } = usePublicResourceConfig()
const { getString } = useStrings()
const currRepoVisibility = repoMetadata?.is_public === true ? RepoVisibility.PUBLIC : RepoVisibility.PRIVATE
const [repoVis, setRepoVis] = useState<RepoVisibility>(currRepoVisibility)
const [enablePublicRepo, setEnablePublicRepo] = useState(false)
const { mutate } = useMutate({
verb: 'PATCH',
path: `/api/v1/repos/${repoMetadata?.path}/+/`
@ -100,19 +100,11 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
},
[space]
)
const { data: systemConfig } = useGet({ path: 'api/v1/system/config' })
useEffect(() => {
if (systemConfig) {
setEnablePublicRepo(systemConfig.public_resource_creation_enabled)
}
}, [systemConfig])
const ModalComponent: React.FC = () => {
return (
<Dialog
className={css.dialogContainer}
style={{ width: 585, maxHeight: '95vh', overflow: 'auto' }}
title={<Text font={{ variation: FontVariation.H4 }}>{getString('changeRepoVis')}</Text>}
isOpen
onClose={hideModal}>
@ -144,7 +136,6 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
</Container>
<Layout.Horizontal className={css.buttonContainer}>
<Button
margin={{ right: 'medium' }}
type="submit"
text={
<StringSubstitute
@ -333,7 +324,7 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
</Container>
</Layout.Horizontal>
</Container>
<Render when={enablePublicRepo && isPublicAccessEnabledOnResources}>
<Render when={allowPublicResourceCreation}>
<Container padding="large" margin={{ bottom: 'medium' }} className={css.generalContainer}>
<Layout.Horizontal padding={{ bottom: 'medium' }}>
<Container className={css.label}>

View File

@ -121,6 +121,9 @@
}
.dialogContainer {
width: 600px;
max-height: 95vh;
overflow: auto;
:global(.bp3-dialog-header) {
margin-bottom: var(--spacing-medium) !important;
}
@ -144,7 +147,7 @@
.buttonContainer {
width: 20%;
padding-top: var(--spacing-xsmall) !important;
gap: 10px;
gap: 15px;
margin-left: auto !important;
}

View File

@ -85,6 +85,12 @@ export const getUsingFetch = <
if (res.status === 401) {
return res.json().then(json => Promise.reject(json))
}
if (res.status === 400) {
return res.text().then(text => Promise.reject(text))
}
if (res.status === 403) {
return res.text().then(text => Promise.reject(text))
}
return res.json()
}
@ -100,6 +106,14 @@ export const getUsingFetch = <
return res.text().then(text => Promise.reject(text))
}
if (res.status === 403) {
return res.text().then(text => Promise.reject(text))
}
if (res.status === 400) {
return res.text().then(text => Promise.reject(text))
}
return res.text()
})
}

View File

@ -468,6 +468,8 @@ export const getProviders = () =>
export const codeOwnersNotFoundMessage = 'CODEOWNERS file not found'
export const codeOwnersNotFoundMessage2 = `path "CODEOWNERS" not found`
export const codeOwnersNotFoundMessage3 = `failed to find node 'CODEOWNERS' in 'main': failed to get tree node: failed to ls file: path "CODEOWNERS" not found`
export const oldCommitRefetchRequired = 'A newer commit is available. Only the latest commit can be merged.'
export const prMergedRefetchRequired = 'Pull request already merged'
export const dryMerge = (
isMounted: React.MutableRefObject<boolean>,
@ -504,6 +506,7 @@ export const dryMerge = (
pullRequestSection: string | undefined,
showError: (message: React.ReactNode, timeout?: number | undefined, key?: string | undefined) => void,
setConflictingFiles: React.Dispatch<React.SetStateAction<string[] | undefined>>,
refetchPullReq: () => void,
setRequiresCommentApproval?: (value: React.SetStateAction<boolean>) => void,
setAtLeastOneReviewerRule?: (value: React.SetStateAction<boolean>) => void,
setReqCodeOwnerApproval?: (value: React.SetStateAction<boolean>) => void,
@ -555,6 +558,11 @@ export const dryMerge = (
setReqCodeOwnerLatestApproval?.(err.requires_code_owners_approval_latest)
setMinReqLatestApproval?.(err.minimum_required_approvals_count_latest)
setConflictingFiles?.(err.conflict_files)
} else if (
err.status === 400 &&
(getErrorMessage(err) === oldCommitRefetchRequired || getErrorMessage(err) === prMergedRefetchRequired)
) {
refetchPullReq()
} else if (
getErrorMessage(err) === codeOwnersNotFoundMessage ||
getErrorMessage(err) === codeOwnersNotFoundMessage2 ||

View File

@ -171,6 +171,7 @@ export interface PullRequestActionsBoxProps extends Pick<GitInfoProps, 'repoMeta
PRStateLoading: boolean
conflictingFiles: string[] | undefined
setConflictingFiles: React.Dispatch<React.SetStateAction<string[] | undefined>>
refetchPullReq: () => void
}
export interface PRMergeOption extends SelectOption {