mirror of
https://github.com/harness/drone.git
synced 2025-05-20 19:09:59 +08:00
[CODE-1116] UI : Protection rules integration with Branch Creation/Deletion and Commits (#824)
This commit is contained in:
parent
5b16c72d4a
commit
274a5a01ab
@ -56,6 +56,18 @@
|
||||
font-size: var(--form-input-font-size);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.warningMessageLayout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.warningMessage {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.newBranchContainer {
|
||||
|
@ -22,3 +22,5 @@ export declare const main: string
|
||||
export declare const newBranch: string
|
||||
export declare const newBranchContainer: string
|
||||
export declare const radioGroup: string
|
||||
export declare const warningMessage: string
|
||||
export declare const warningMessageLayout: string
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Dialog, Intent } from '@blueprintjs/core'
|
||||
import * as yup from 'yup'
|
||||
import {
|
||||
@ -22,6 +22,7 @@ import {
|
||||
ButtonProps,
|
||||
Container,
|
||||
Layout,
|
||||
Text,
|
||||
FlexExpander,
|
||||
Formik,
|
||||
FormikForm,
|
||||
@ -32,10 +33,12 @@ import {
|
||||
} from '@harnessio/uicore'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import cx from 'classnames'
|
||||
import { FontVariation } from '@harnessio/design-system'
|
||||
import { FontVariation, Color } from '@harnessio/design-system'
|
||||
import { useMutate } from 'restful-react'
|
||||
import { get } from 'lodash-es'
|
||||
import { Render } from 'react-jsx-match'
|
||||
import { useModalHook } from 'hooks/useModalHook'
|
||||
import { useRuleViolationCheck } from 'hooks/useRuleViolationCheck'
|
||||
import { String, useStrings } from 'framework/strings'
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import type { OpenapiCommitFilesRequest, TypesListCommitResponse } from 'services/code'
|
||||
@ -82,10 +85,20 @@ export function useCommitModal({
|
||||
const { getString } = useStrings()
|
||||
const [targetBranchOption, setTargetBranchOption] = useState(CommitToGitRefOption.DIRECTLY)
|
||||
const { showError, showSuccess } = useToaster()
|
||||
const { violation, bypassable, bypassed, setAllStates, resetViolation } = useRuleViolationCheck()
|
||||
const [disableCTA, setDisableCTA] = useState(false)
|
||||
const { mutate, loading } = useMutate<TypesListCommitResponse>({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/commits`
|
||||
})
|
||||
const { mutate: dryRunCall } = useMutate({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/commits`
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
dryRun(CommitToGitRefOption.DIRECTLY)
|
||||
}, [])
|
||||
|
||||
const handleSubmit = (formData: FormData) => {
|
||||
try {
|
||||
@ -101,28 +114,73 @@ export function useCommitModal({
|
||||
}
|
||||
],
|
||||
branch: gitRef,
|
||||
new_branch: formData.newBranch,
|
||||
new_branch: targetBranchOption === CommitToGitRefOption.NEW_BRANCH ? formData.newBranch : '',
|
||||
title: formData.title || commitTitlePlaceHolder,
|
||||
message: formData.message
|
||||
message: formData.message,
|
||||
bypass_rules: bypassed
|
||||
}
|
||||
|
||||
mutate(data)
|
||||
.then(response => {
|
||||
hideModal()
|
||||
onSuccess(response, formData.newBranch)
|
||||
onSuccess(response, targetBranchOption === CommitToGitRefOption.NEW_BRANCH ? formData.newBranch : '')
|
||||
|
||||
if (commitAction === GitCommitAction.DELETE) {
|
||||
showSuccess(getString('fileDeleted').replace('__path__', resourcePath))
|
||||
}
|
||||
})
|
||||
.catch(_error => {
|
||||
showError(getErrorMessage(_error), 0, getString('failedToCreateRepo'))
|
||||
if (_error.status === 422) {
|
||||
setAllStates({
|
||||
violation: true,
|
||||
bypassed: true,
|
||||
bypassable: _error?.data?.violations[0]?.bypassable
|
||||
})
|
||||
} else showError(getErrorMessage(_error), 0, getString('failedToCreateRepo'))
|
||||
})
|
||||
} catch (exception) {
|
||||
showError(getErrorMessage(exception), 0, getString('failedToCreateRepo'))
|
||||
}
|
||||
}
|
||||
|
||||
const dryRun = async (targetBranch: CommitToGitRefOption) => {
|
||||
resetViolation()
|
||||
setDisableCTA(false)
|
||||
if (targetBranch === CommitToGitRefOption.DIRECTLY) {
|
||||
try {
|
||||
const data: OpenapiCommitFilesRequest = {
|
||||
actions: [
|
||||
{
|
||||
action: commitAction,
|
||||
path: oldResourcePath || resourcePath,
|
||||
payload: `${oldResourcePath ? `file://${resourcePath}\n` : ''}${payload}`,
|
||||
sha
|
||||
}
|
||||
],
|
||||
branch: gitRef,
|
||||
new_branch: '',
|
||||
title: '',
|
||||
message: '',
|
||||
bypass_rules: false,
|
||||
dry_run_rules: true
|
||||
}
|
||||
|
||||
const response = await dryRunCall(data)
|
||||
|
||||
if (response?.rule_violations?.length) {
|
||||
setAllStates({
|
||||
violation: true,
|
||||
bypassed: false,
|
||||
bypassable: response?.rule_violations[0]?.bypassable
|
||||
})
|
||||
setDisableCTA(!response?.rule_violations[0]?.bypassable)
|
||||
}
|
||||
} catch (exception) {
|
||||
showError(getErrorMessage(exception), 0, getString('failedToCreateRepo'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
@ -186,14 +244,43 @@ export function useCommitModal({
|
||||
label=""
|
||||
onChange={e => {
|
||||
setTargetBranchOption(get(e.target, 'defaultValue') as unknown as CommitToGitRefOption)
|
||||
dryRun(get(e.target, 'defaultValue') as unknown as CommitToGitRefOption)
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
label: <String stringID="commitDirectlyTo" vars={{ gitRef }} useRichText />,
|
||||
label: (
|
||||
<Layout.Horizontal className={css.warningMessageLayout}>
|
||||
<String stringID="commitDirectlyTo" vars={{ gitRef }} useRichText />
|
||||
<Render when={violation && targetBranchOption === CommitToGitRefOption.DIRECTLY}>
|
||||
<Layout.Horizontal className={css.warningMessage}>
|
||||
<Icon intent={Intent.WARNING} name="danger-icon" size={16} />
|
||||
<Text font={{ variation: FontVariation.BODY2 }} color={Color.RED_800}>
|
||||
{bypassable
|
||||
? getString('branchProtection.commitDirectlyBlockText')
|
||||
: getString('branchProtection.commitNewBranchBlockText')}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
</Render>
|
||||
</Layout.Horizontal>
|
||||
),
|
||||
value: CommitToGitRefOption.DIRECTLY
|
||||
},
|
||||
{
|
||||
label: <String stringID="commitToNewBranch" useRichText />,
|
||||
label: (
|
||||
<Layout.Horizontal className={css.warningMessageLayout}>
|
||||
<String stringID="commitToNewBranch" useRichText />
|
||||
<Render when={violation && targetBranchOption === CommitToGitRefOption.NEW_BRANCH}>
|
||||
<Layout.Horizontal className={css.warningMessage}>
|
||||
<Icon intent={Intent.WARNING} name="danger-icon" size={16} />
|
||||
<Text font={{ variation: FontVariation.BODY2 }} color={Color.RED_800}>
|
||||
{bypassable
|
||||
? getString('branchProtection.commitNewBranchAlertText')
|
||||
: getString('branchProtection.commitNewBranchBlockText')}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
</Render>
|
||||
</Layout.Horizontal>
|
||||
),
|
||||
value: CommitToGitRefOption.NEW_BRANCH
|
||||
}
|
||||
]}
|
||||
@ -209,6 +296,9 @@ export function useCommitModal({
|
||||
dataTooltipId: 'enterNewBranchName'
|
||||
}}
|
||||
inputGroup={{ autoFocus: true }}
|
||||
onChange={() => {
|
||||
setAllStates({ violation: false, bypassable: false, bypassed: false })
|
||||
}}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
@ -216,12 +306,22 @@ export function useCommitModal({
|
||||
</Container>
|
||||
|
||||
<Layout.Horizontal spacing="small" padding={{ right: 'xxlarge', top: 'xxlarge', bottom: 'large' }}>
|
||||
{!bypassable ? (
|
||||
<Button
|
||||
type="submit"
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
text={getString('commit')}
|
||||
disabled={loading}
|
||||
disabled={loading || disableCTA}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
intent={Intent.DANGER}
|
||||
disabled={loading}
|
||||
type="submit"
|
||||
variation={ButtonVariation.SECONDARY}
|
||||
text={getString('branchProtection.commitNewBranchAlertBtn')}
|
||||
/>
|
||||
)}
|
||||
<Button text={getString('cancel')} variation={ButtonVariation.LINK} onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
|
||||
|
@ -30,6 +30,12 @@
|
||||
margin-right: var(--spacing-10) !important;
|
||||
}
|
||||
|
||||
.warningMessage {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// .branchSourceDesc {
|
||||
// color: var(--grey-400) !important;
|
||||
// font-size: var(--form-input-font-size) !important;
|
||||
|
@ -24,3 +24,4 @@ export declare const maxContainer: string
|
||||
export declare const popoverContainer: string
|
||||
export declare const selectContainer: string
|
||||
export declare const title: string
|
||||
export declare const warningMessage: string
|
||||
|
@ -29,13 +29,15 @@ import {
|
||||
useToaster,
|
||||
FormInput,
|
||||
Label,
|
||||
Text,
|
||||
ButtonVariation,
|
||||
StringSubstitute
|
||||
} from '@harnessio/uicore'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { FontVariation } from '@harnessio/design-system'
|
||||
import { FontVariation, Color } from '@harnessio/design-system'
|
||||
import { useMutate } from 'restful-react'
|
||||
import { get } from 'lodash-es'
|
||||
import { Render } from 'react-jsx-match'
|
||||
import { useModalHook } from 'hooks/useModalHook'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { getErrorMessage, permissionProps } from 'utils/Utils'
|
||||
@ -43,6 +45,7 @@ import { GitInfoProps, normalizeGitRef, isGitBranchNameValid } from 'utils/GitUt
|
||||
import { BranchTagSelect } from 'components/BranchTagSelect/BranchTagSelect'
|
||||
import type { RepoBranch } from 'services/code'
|
||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||
import { useRuleViolationCheck } from 'hooks/useRuleViolationCheck'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import css from './CreateBranchModal.module.scss'
|
||||
|
||||
@ -79,6 +82,7 @@ export function useCreateBranchModal({
|
||||
const { getString } = useStrings()
|
||||
const [sourceBranch, setSourceBranch] = useState(suggestedSourceBranch || (repoMetadata.default_branch as string))
|
||||
const { showError, showSuccess } = useToaster()
|
||||
const { violation, bypassable, bypassed, setAllStates } = useRuleViolationCheck()
|
||||
const { mutate: createBranch, loading } = useMutate<RepoBranch>({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/branches`
|
||||
@ -88,7 +92,8 @@ export function useCreateBranchModal({
|
||||
try {
|
||||
createBranch({
|
||||
name,
|
||||
target: normalizeGitRef(refIsATag ? `refs/tags/${sourceBranch}` : sourceBranch)
|
||||
target: normalizeGitRef(refIsATag ? `refs/tags/${sourceBranch}` : sourceBranch),
|
||||
bypass_rules: bypassed
|
||||
})
|
||||
.then(response => {
|
||||
hideModal()
|
||||
@ -106,7 +111,13 @@ export function useCreateBranchModal({
|
||||
}
|
||||
})
|
||||
.catch(_error => {
|
||||
showError(getErrorMessage(_error), 0, 'failedToCreateBranch')
|
||||
if (_error.status === 422) {
|
||||
setAllStates({
|
||||
violation: true,
|
||||
bypassed: true,
|
||||
bypassable: _error?.data?.violations[0]?.bypassable
|
||||
})
|
||||
} else showError(getErrorMessage(_error), 0, 'failedToCreateBranch')
|
||||
})
|
||||
} catch (exception) {
|
||||
showError(getErrorMessage(exception), 0, 'failedToCreateBranch')
|
||||
@ -154,6 +165,9 @@ export function useCreateBranchModal({
|
||||
dataTooltipId: 'repositoryBranchTextField'
|
||||
}}
|
||||
inputGroup={{ autoFocus: true }}
|
||||
onChange={() => {
|
||||
setAllStates({ violation: false, bypassable: false, bypassed: false })
|
||||
}}
|
||||
/>
|
||||
<Container margin={{ top: 'medium' }}>
|
||||
<Label className={css.label}>{getString('basedOn')}</Label>
|
||||
@ -176,17 +190,37 @@ export function useCreateBranchModal({
|
||||
spacing="small"
|
||||
padding={{ right: 'xxlarge', top: 'xxlarge', bottom: 'large' }}
|
||||
style={{ alignItems: 'center' }}>
|
||||
{!bypassable ? (
|
||||
<Button
|
||||
type="submit"
|
||||
text={getString('createBranch')}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
disabled={loading}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
intent={Intent.DANGER}
|
||||
disabled={loading}
|
||||
type="submit"
|
||||
variation={ButtonVariation.SECONDARY}
|
||||
text={getString('branchProtection.createBranchAlertBtn')}
|
||||
/>
|
||||
)}
|
||||
<Button text={getString('cancel')} variation={ButtonVariation.LINK} onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||
</Layout.Horizontal>
|
||||
<Render when={violation}>
|
||||
<Layout.Horizontal className={css.warningMessage}>
|
||||
<Icon intent={Intent.WARNING} name="danger-icon" size={16} />
|
||||
<Text font={{ variation: FontVariation.BODY2 }} color={Color.RED_800}>
|
||||
{bypassable
|
||||
? getString('branchProtection.createBranchAlertText')
|
||||
: getString('branchProtection.createBranchBlockText')}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
</Render>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
</Container>
|
||||
|
@ -54,9 +54,19 @@ export interface StringsMap {
|
||||
'branchProtection.blockBranchDeletion': string
|
||||
'branchProtection.blockBranchDeletionText': string
|
||||
'branchProtection.bypassList': string
|
||||
'branchProtection.commitDirectlyBlockText': string
|
||||
'branchProtection.commitNewBranchAlertBtn': string
|
||||
'branchProtection.commitNewBranchAlertText': string
|
||||
'branchProtection.commitNewBranchBlockText': string
|
||||
'branchProtection.create': string
|
||||
'branchProtection.createBranchAlertBtn': string
|
||||
'branchProtection.createBranchAlertText': string
|
||||
'branchProtection.createBranchBlockText': string
|
||||
'branchProtection.createRule': string
|
||||
'branchProtection.defaultBranch': string
|
||||
'branchProtection.deleteBranchAlertBtn': string
|
||||
'branchProtection.deleteBranchAlertText': string
|
||||
'branchProtection.deleteBranchBlockText': string
|
||||
'branchProtection.deleteProtectionRule': string
|
||||
'branchProtection.deleteRule': string
|
||||
'branchProtection.deleteText': string
|
||||
|
@ -24,22 +24,25 @@ import { useConfirmationDialog } from './useConfirmationDialog'
|
||||
|
||||
export interface UseConfirmActionDialogProps {
|
||||
message: React.ReactElement
|
||||
childtag?: React.ReactElement
|
||||
intent?: Intent
|
||||
title?: string
|
||||
confirmText?: string
|
||||
cancelText?: string
|
||||
action: (params?: Unknown) => void
|
||||
persistDialog?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use useConfirmAct() hook instead
|
||||
*/
|
||||
export const useConfirmAction = (props: UseConfirmActionDialogProps) => {
|
||||
const { title, message, confirmText, cancelText, intent, action } = props
|
||||
const { title, message, confirmText, cancelText, intent, childtag, action, persistDialog } = props
|
||||
const { getString } = useStrings()
|
||||
const [params, setParams] = useState<Unknown>()
|
||||
const { openDialog } = useConfirmationDialog({
|
||||
intent,
|
||||
persistDialog: persistDialog,
|
||||
titleText: title || getString('confirmation'),
|
||||
contentText: message,
|
||||
confirmButtonText: confirmText || getString('confirm'),
|
||||
@ -49,7 +52,8 @@ export const useConfirmAction = (props: UseConfirmActionDialogProps) => {
|
||||
if (isConfirmed) {
|
||||
action(params)
|
||||
}
|
||||
}
|
||||
},
|
||||
children: childtag || <></>
|
||||
})
|
||||
const confirm = useCallback(
|
||||
(_params?: Unknown) => {
|
||||
|
@ -33,6 +33,7 @@ export interface UseConfirmationDialogProps {
|
||||
canEscapeKeyClose?: boolean
|
||||
children?: JSX.Element
|
||||
className?: string
|
||||
persistDialog?: boolean
|
||||
}
|
||||
|
||||
export interface UseConfirmationDialogReturn {
|
||||
@ -54,7 +55,8 @@ export const useConfirmationDialog = (props: UseConfirmationDialogProps): UseCon
|
||||
canOutsideClickClose,
|
||||
canEscapeKeyClose,
|
||||
children,
|
||||
className
|
||||
className,
|
||||
persistDialog
|
||||
} = props
|
||||
|
||||
const [showModal, hideModal] = useModalHook(() => {
|
||||
@ -81,9 +83,10 @@ export const useConfirmationDialog = (props: UseConfirmationDialogProps): UseCon
|
||||
const onClose = React.useCallback(
|
||||
(isConfirmed: boolean): void => {
|
||||
onCloseDialog?.(isConfirmed)
|
||||
hideModal()
|
||||
if (!isConfirmed) hideModal()
|
||||
else if (persistDialog) showModal()
|
||||
},
|
||||
[hideModal, onCloseDialog]
|
||||
[hideModal, onCloseDialog, persistDialog]
|
||||
)
|
||||
|
||||
return {
|
||||
|
68
web/src/hooks/useRuleViolationCheck.tsx
Normal file
68
web/src/hooks/useRuleViolationCheck.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { useReducer } from 'react'
|
||||
|
||||
enum ActionTypes {
|
||||
SET_VIOLATION = 'SET_VIOLATION',
|
||||
SET_BYPASSED = 'SET_BYPASSED',
|
||||
SET_BYPASSABLE = 'SET_BYPASSABLE',
|
||||
SET_ALL_STATES = 'SET_ALL_STATES'
|
||||
}
|
||||
|
||||
interface ViolationState {
|
||||
violation: boolean
|
||||
bypassable: boolean
|
||||
bypassed: boolean
|
||||
}
|
||||
|
||||
type ViolationAction =
|
||||
| { type: ActionTypes.SET_VIOLATION; payload: boolean }
|
||||
| { type: ActionTypes.SET_BYPASSED; payload: boolean }
|
||||
| { type: ActionTypes.SET_BYPASSABLE; payload: boolean }
|
||||
| { type: ActionTypes.SET_ALL_STATES; payload: Partial<ViolationState> }
|
||||
|
||||
const initialState: ViolationState = {
|
||||
violation: false,
|
||||
bypassable: false,
|
||||
bypassed: false
|
||||
}
|
||||
|
||||
const reducer = (state: ViolationState, action: ViolationAction): ViolationState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SET_VIOLATION:
|
||||
return { ...state, violation: action.payload }
|
||||
case ActionTypes.SET_BYPASSABLE:
|
||||
return { ...state, bypassed: action.payload }
|
||||
case ActionTypes.SET_BYPASSED:
|
||||
return { ...state, bypassable: action.payload }
|
||||
case ActionTypes.SET_ALL_STATES:
|
||||
return {
|
||||
...state,
|
||||
violation: action.payload.violation !== undefined ? action.payload.violation : state.violation,
|
||||
bypassable: action.payload.bypassable !== undefined ? action.payload.bypassable : state.bypassable,
|
||||
bypassed: action.payload.bypassed !== undefined ? action.payload.bypassed : state.bypassed
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export const useRuleViolationCheck = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
const setViolation = (value: boolean) => dispatch({ type: ActionTypes.SET_VIOLATION, payload: value })
|
||||
const setBypassable = (value: boolean) => dispatch({ type: ActionTypes.SET_BYPASSABLE, payload: value })
|
||||
const setBypassed = (value: boolean) => dispatch({ type: ActionTypes.SET_BYPASSED, payload: value })
|
||||
const setAllStates = (payload: Partial<ViolationState>) => {
|
||||
dispatch({ type: ActionTypes.SET_ALL_STATES, payload })
|
||||
}
|
||||
const resetViolation = () => setAllStates({ violation: false, bypassable: false, bypassed: false })
|
||||
|
||||
return {
|
||||
violation: state.violation,
|
||||
setViolation,
|
||||
bypassable: state.bypassable,
|
||||
setBypassable,
|
||||
bypassed: state.bypassed,
|
||||
setBypassed,
|
||||
setAllStates,
|
||||
resetViolation
|
||||
}
|
||||
}
|
@ -913,6 +913,16 @@ branchProtection:
|
||||
mergePrAlertTitle: Merge pull request alert
|
||||
mergePrAlertText: 'Merge cannot be completed. {{ruleCount}} branch rules failed: '
|
||||
mergeCheckboxAlert: Bypass branch rules and merge
|
||||
createBranchAlertBtn: Bypass rules and create branch
|
||||
createBranchAlertText: Some rules will be bypassed by creating branch
|
||||
createBranchBlockText: Some rules don't allow you to create branch
|
||||
deleteBranchAlertBtn: Bypass rule and confirm delete
|
||||
deleteBranchAlertText: Some rules will be bypassed while deleting branch
|
||||
deleteBranchBlockText: Some rules don't allow you to delete branch
|
||||
commitNewBranchAlertBtn: Bypass rules and commit via new branch
|
||||
commitNewBranchAlertText: Some rules will be bypassed to commit by creating branch
|
||||
commitNewBranchBlockText: Some rules don't allow you to create new branch for commit
|
||||
commitDirectlyBlockText: Some rules don't allow you to commit directly
|
||||
codeOwner:
|
||||
title: Code Owner
|
||||
changesRequested: '{count} {count|1:change,changes} requested'
|
||||
|
@ -80,6 +80,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.warningMessage {
|
||||
order: 3;
|
||||
display: flex !important;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin-top: 1rem !important;
|
||||
}
|
||||
|
||||
.popover {
|
||||
:global {
|
||||
.bp3-popover-content {
|
||||
|
@ -25,3 +25,4 @@ export declare const row: string
|
||||
export declare const rowText: string
|
||||
export declare const spacer: string
|
||||
export declare const table: string
|
||||
export declare const warningMessage: string
|
||||
|
@ -18,6 +18,7 @@ import React, { useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Container,
|
||||
TableV2 as Table,
|
||||
Layout,
|
||||
Text,
|
||||
Avatar,
|
||||
Tag,
|
||||
@ -25,8 +26,10 @@ import {
|
||||
StringSubstitute,
|
||||
useIsMounted
|
||||
} from '@harnessio/uicore'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { noop } from 'lodash-es'
|
||||
import { Color, Intent } from '@harnessio/design-system'
|
||||
import { Color, Intent, FontVariation } from '@harnessio/design-system'
|
||||
import { Render } from 'react-jsx-match'
|
||||
import type { CellProps, Column } from 'react-table'
|
||||
import { Link, useHistory } from 'react-router-dom'
|
||||
import cx from 'classnames'
|
||||
@ -43,6 +46,7 @@ import type {
|
||||
import { CommitActions } from 'components/CommitActions/CommitActions'
|
||||
import { formatDate, getErrorMessage } from 'utils/Utils'
|
||||
import { useConfirmAction } from 'hooks/useConfirmAction'
|
||||
import { useRuleViolationCheck } from 'hooks/useRuleViolationCheck'
|
||||
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
||||
import { CommitDivergence } from 'components/CommitDivergence/CommitDivergence'
|
||||
import { makeDiffRefs } from 'utils/GitUtils'
|
||||
@ -167,19 +171,23 @@ export function BranchesContent({ repoMetadata, searchTerm = '', branches, onDel
|
||||
id: 'action',
|
||||
width: '30px',
|
||||
Cell: ({ row }: CellProps<RepoBranch>) => {
|
||||
const { violation, bypassable, bypassed, setAllStates } = useRuleViolationCheck()
|
||||
const [persistModal, setPersistModal] = useState(true)
|
||||
const { mutate: deleteBranch } = useMutate({
|
||||
verb: 'DELETE',
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/branches/${row.original.name}`
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/branches/${row.original.name}?bypass_rules=${bypassed}`
|
||||
})
|
||||
const { showSuccess, showError } = useToaster()
|
||||
const confirmDeleteBranch = useConfirmAction({
|
||||
title: getString('deleteBranch'),
|
||||
confirmText: getString('delete'),
|
||||
confirmText: !bypassable ? getString('delete') : getString('branchProtection.deleteBranchAlertBtn'),
|
||||
intent: Intent.DANGER,
|
||||
message: <String useRichText stringID="deleteBranchConfirm" vars={{ name: row.original.name }} />,
|
||||
persistDialog: persistModal,
|
||||
action: async () => {
|
||||
deleteBranch({})
|
||||
.then(() => {
|
||||
setPersistModal(false)
|
||||
showSuccess(
|
||||
<StringSubstitute
|
||||
str={getString('branchDeleted')}
|
||||
@ -192,9 +200,27 @@ export function BranchesContent({ repoMetadata, searchTerm = '', branches, onDel
|
||||
onDeleteSuccess()
|
||||
})
|
||||
.catch(error => {
|
||||
showError(getErrorMessage(error), 0, 'failedToDeleteBranch')
|
||||
if (error.status === 422) {
|
||||
setAllStates({
|
||||
violation: true,
|
||||
bypassed: true,
|
||||
bypassable: error?.data?.violations[0]?.bypassable
|
||||
})
|
||||
}
|
||||
} else showError(getErrorMessage(error), 0, 'failedToDeleteBranch')
|
||||
})
|
||||
},
|
||||
childtag: (
|
||||
<Render when={violation}>
|
||||
<Layout.Horizontal className={css.warningMessage}>
|
||||
<Icon intent={Intent.WARNING} name="danger-icon" size={16} />
|
||||
<Text font={{ variation: FontVariation.BODY2 }} color={Color.RED_800}>
|
||||
{bypassable
|
||||
? getString('branchProtection.deleteBranchAlertText')
|
||||
: getString('branchProtection.deleteBranchBlockText')}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
</Render>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
|
@ -184,6 +184,8 @@ export interface OpenapiCommitFilesRequest {
|
||||
message?: string
|
||||
new_branch?: string
|
||||
title?: string
|
||||
bypass_rules?: boolean
|
||||
dry_run_rules?: boolean
|
||||
}
|
||||
|
||||
export type OpenapiContent = RepoFileContent | OpenapiDirContent | RepoSymlinkContent | RepoSubmoduleContent
|
||||
|
@ -867,7 +867,7 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/GitrpcBlamePart'
|
||||
$ref: '#/components/schemas/GitBlamePart'
|
||||
type: array
|
||||
description: OK
|
||||
'401':
|
||||
@ -1047,6 +1047,13 @@ paths:
|
||||
delete:
|
||||
operationId: deleteBranch
|
||||
parameters:
|
||||
- description: Bypass rule violations if possible.
|
||||
in: query
|
||||
name: bypass_rules
|
||||
required: false
|
||||
schema:
|
||||
default: false
|
||||
type: boolean
|
||||
- in: path
|
||||
name: repo_ref
|
||||
required: true
|
||||
@ -1814,7 +1821,7 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/GitrpcFileDiff'
|
||||
$ref: '#/components/schemas/GitFileDiff'
|
||||
type: array
|
||||
text/plain:
|
||||
schema:
|
||||
@ -4413,6 +4420,8 @@ paths:
|
||||
$ref: '#/components/schemas/EnumRuleState'
|
||||
type:
|
||||
$ref: '#/components/schemas/OpenapiRuleType'
|
||||
uid:
|
||||
type: string
|
||||
type: object
|
||||
responses:
|
||||
'200':
|
||||
@ -4648,6 +4657,13 @@ paths:
|
||||
delete:
|
||||
operationId: deleteTag
|
||||
parameters:
|
||||
- description: Bypass rule violations if possible.
|
||||
in: query
|
||||
name: bypass_rules
|
||||
required: false
|
||||
schema:
|
||||
default: false
|
||||
type: boolean
|
||||
- in: path
|
||||
name: repo_ref
|
||||
required: true
|
||||
@ -6899,8 +6915,8 @@ components:
|
||||
EnumMergeMethod:
|
||||
enum:
|
||||
- merge
|
||||
- squash
|
||||
- rebase
|
||||
- squash
|
||||
type: string
|
||||
EnumParentResourceType:
|
||||
enum:
|
||||
@ -7000,22 +7016,22 @@ components:
|
||||
- tag_deleted
|
||||
- tag_updated
|
||||
type: string
|
||||
GitrpcBlamePart:
|
||||
GitBlamePart:
|
||||
properties:
|
||||
commit:
|
||||
$ref: '#/components/schemas/GitrpcCommit'
|
||||
$ref: '#/components/schemas/GitCommit'
|
||||
lines:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
GitrpcCommit:
|
||||
GitCommit:
|
||||
properties:
|
||||
author:
|
||||
$ref: '#/components/schemas/GitrpcSignature'
|
||||
$ref: '#/components/schemas/GitSignature'
|
||||
committer:
|
||||
$ref: '#/components/schemas/GitrpcSignature'
|
||||
$ref: '#/components/schemas/GitSignature'
|
||||
message:
|
||||
type: string
|
||||
sha:
|
||||
@ -7023,14 +7039,14 @@ components:
|
||||
title:
|
||||
type: string
|
||||
type: object
|
||||
GitrpcFileAction:
|
||||
GitFileAction:
|
||||
enum:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
- MOVE
|
||||
type: string
|
||||
GitrpcFileDiff:
|
||||
GitFileDiff:
|
||||
properties:
|
||||
additions:
|
||||
type: integer
|
||||
@ -7056,30 +7072,30 @@ components:
|
||||
sha:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/GitrpcFileDiffStatus'
|
||||
$ref: '#/components/schemas/GitFileDiffStatus'
|
||||
type: object
|
||||
GitrpcFileDiffStatus:
|
||||
GitFileDiffStatus:
|
||||
type: string
|
||||
GitrpcIdentity:
|
||||
GitIdentity:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
GitrpcPathDetails:
|
||||
GitPathDetails:
|
||||
properties:
|
||||
last_commit:
|
||||
$ref: '#/components/schemas/GitrpcCommit'
|
||||
$ref: '#/components/schemas/GitCommit'
|
||||
path:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
type: object
|
||||
GitrpcSignature:
|
||||
GitSignature:
|
||||
properties:
|
||||
identity:
|
||||
$ref: '#/components/schemas/GitrpcIdentity'
|
||||
$ref: '#/components/schemas/GitIdentity'
|
||||
when:
|
||||
format: date-time
|
||||
type: string
|
||||
@ -7191,6 +7207,10 @@ components:
|
||||
type: array
|
||||
branch:
|
||||
type: string
|
||||
bypass_rules:
|
||||
type: boolean
|
||||
dry_run_rules:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
new_branch:
|
||||
@ -7227,6 +7247,8 @@ components:
|
||||
type: string
|
||||
OpenapiCreateBranchRequest:
|
||||
properties:
|
||||
bypass_rules:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
target:
|
||||
@ -7318,6 +7340,8 @@ components:
|
||||
type: object
|
||||
OpenapiCreateTagRequest:
|
||||
properties:
|
||||
bypass_rules:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
name:
|
||||
@ -7477,8 +7501,6 @@ components:
|
||||
type: string
|
||||
decision:
|
||||
$ref: '#/components/schemas/EnumPullReqReviewDecision'
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
OpenapiReviewerAddPullReqRequest:
|
||||
properties:
|
||||
@ -7518,8 +7540,6 @@ components:
|
||||
properties:
|
||||
is_draft:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
state:
|
||||
$ref: '#/components/schemas/EnumPullReqState'
|
||||
type: object
|
||||
@ -7794,7 +7814,7 @@ components:
|
||||
RepoCommitFileAction:
|
||||
properties:
|
||||
action:
|
||||
$ref: '#/components/schemas/GitrpcFileAction'
|
||||
$ref: '#/components/schemas/GitFileAction'
|
||||
encoding:
|
||||
$ref: '#/components/schemas/EnumContentEncodingType'
|
||||
path:
|
||||
@ -7861,7 +7881,7 @@ components:
|
||||
properties:
|
||||
details:
|
||||
items:
|
||||
$ref: '#/components/schemas/GitrpcPathDetails'
|
||||
$ref: '#/components/schemas/GitPathDetails'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
@ -7994,6 +8014,12 @@ components:
|
||||
properties:
|
||||
commit_id:
|
||||
type: string
|
||||
dry_run_rules:
|
||||
type: boolean
|
||||
rule_violations:
|
||||
items:
|
||||
$ref: '#/components/schemas/TypesRuleViolations'
|
||||
type: array
|
||||
type: object
|
||||
TypesConnector:
|
||||
properties:
|
||||
@ -8159,6 +8185,10 @@ components:
|
||||
type: object
|
||||
TypesMergeResponse:
|
||||
properties:
|
||||
allowed_methods:
|
||||
items:
|
||||
$ref: '#/components/schemas/EnumMergeMethod'
|
||||
type: array
|
||||
branch_deleted:
|
||||
type: boolean
|
||||
conflict_files:
|
||||
@ -8457,6 +8487,8 @@ components:
|
||||
type: string
|
||||
TypesRuleViolations:
|
||||
properties:
|
||||
bypassable:
|
||||
type: boolean
|
||||
bypassed:
|
||||
type: boolean
|
||||
rule:
|
||||
|
Loading…
Reference in New Issue
Block a user