mirror of
https://github.com/harness/drone.git
synced 2025-05-22 03:49:54 +08:00
feat: [CODE-2765]: add Branch rules support on Account, Org, and Project level (#3143)
* fix: [CODE-2765] prettier * fix: [CODE-2765] fix lint * feat: [CODE-1509] added BranchProtectionHeader with permission utils * feat: [CODE-1509] added BranchProtectionForm with permissions props * feat: [CODE-1509] added BranchProtectionHeader with permission util abstracted * feat: [CODE-1509] added BranchProtectionListing with permission props * feat: [CODE-1509] added BranchProtectionHeader with permission props * fix: [CODE-1509] lint * feat: [CODE-1509] permission utility * feat: [CODE-2765]: add Branch rules support on Account, Org, and Project level
This commit is contained in:
parent
fa675f322c
commit
de91e0ddef
@ -52,7 +52,7 @@ module.exports = {
|
|||||||
'./Webhooks': './src/pages/Webhooks/Webhooks.tsx',
|
'./Webhooks': './src/pages/Webhooks/Webhooks.tsx',
|
||||||
'./WebhookNew': './src/pages/WebhookNew/WebhookNew.tsx',
|
'./WebhookNew': './src/pages/WebhookNew/WebhookNew.tsx',
|
||||||
'./Search': './src/pages/Search/CodeSearchPage.tsx',
|
'./Search': './src/pages/Search/CodeSearchPage.tsx',
|
||||||
'./Labels': './src/pages/ManageSpace/ManageLabels/ManageLabels.tsx',
|
'./Labels': './src/pages/ManageSpace/ManageRepositories/ManageRepositories.tsx',
|
||||||
'./WebhookDetails': './src/pages/WebhookDetails/WebhookDetails.tsx',
|
'./WebhookDetails': './src/pages/WebhookDetails/WebhookDetails.tsx',
|
||||||
'./NewRepoModalButton': './src/components/NewRepoModalButton/NewRepoModalButton.tsx',
|
'./NewRepoModalButton': './src/components/NewRepoModalButton/NewRepoModalButton.tsx',
|
||||||
'./HAREnterpriseApp': './src/ar/app/EnterpriseApp.tsx',
|
'./HAREnterpriseApp': './src/ar/app/EnterpriseApp.tsx',
|
||||||
|
@ -71,7 +71,12 @@ export interface CODERoutes extends CDERoutes, ARRoutes {
|
|||||||
toCODEHome: () => string
|
toCODEHome: () => string
|
||||||
|
|
||||||
toCODESpaceAccessControl: (args: Required<Pick<CODEProps, 'space'>>) => string
|
toCODESpaceAccessControl: (args: Required<Pick<CODEProps, 'space'>>) => string
|
||||||
toCODESpaceSettings: (args: RequiredField<Pick<CODEProps, 'space' | 'settingSection'>, 'space'>) => string
|
toCODESpaceSettings: (
|
||||||
|
args: RequiredField<Pick<CODEProps, 'space' | 'settingSection' | 'ruleId' | 'settingSectionMode'>, 'space'>
|
||||||
|
) => string
|
||||||
|
toCODEManageRepositories: (
|
||||||
|
args: RequiredField<Pick<CODEProps, 'space' | 'settingSection' | 'ruleId' | 'settingSectionMode'>, 'space'>
|
||||||
|
) => string
|
||||||
toCODEPipelines: (args: Required<Pick<CODEProps, 'repoPath'>>) => string
|
toCODEPipelines: (args: Required<Pick<CODEProps, 'repoPath'>>) => string
|
||||||
toCODEPipelineEdit: (args: Required<Pick<CODEProps, 'repoPath' | 'pipeline'>>) => string
|
toCODEPipelineEdit: (args: Required<Pick<CODEProps, 'repoPath' | 'pipeline'>>) => string
|
||||||
toCODEPipelineSettings: (args: Required<Pick<CODEProps, 'repoPath' | 'pipeline'>>) => string
|
toCODEPipelineSettings: (args: Required<Pick<CODEProps, 'repoPath' | 'pipeline'>>) => string
|
||||||
@ -104,7 +109,6 @@ export interface CODERoutes extends CDERoutes, ARRoutes {
|
|||||||
args: RequiredField<Pick<CODEProps, 'repoPath' | 'settingSection' | 'ruleId' | 'settingSectionMode'>, 'repoPath'>
|
args: RequiredField<Pick<CODEProps, 'repoPath' | 'settingSection' | 'ruleId' | 'settingSectionMode'>, 'repoPath'>
|
||||||
) => string
|
) => string
|
||||||
toCODESpaceSearch: (args: Required<Pick<CODEProps, 'space'>>) => string
|
toCODESpaceSearch: (args: Required<Pick<CODEProps, 'space'>>) => string
|
||||||
toCODESpaceLabels: (args: Required<Pick<CODEProps, 'space'>>) => string
|
|
||||||
toCODERepositorySearch: (args: Required<Pick<CODEProps, 'repoPath'>>) => string
|
toCODERepositorySearch: (args: Required<Pick<CODEProps, 'repoPath'>>) => string
|
||||||
toCODESemanticSearch: (args: Required<Pick<CODEProps, 'repoPath'>>) => string
|
toCODESemanticSearch: (args: Required<Pick<CODEProps, 'repoPath'>>) => string
|
||||||
toCODEExecutions: (args: Required<Pick<CODEProps, 'repoPath' | 'pipeline'>>) => string
|
toCODEExecutions: (args: Required<Pick<CODEProps, 'repoPath' | 'pipeline'>>) => string
|
||||||
@ -128,8 +132,14 @@ export const routes: CODERoutes = {
|
|||||||
toCODEHome: () => `/`,
|
toCODEHome: () => `/`,
|
||||||
|
|
||||||
toCODESpaceAccessControl: ({ space }) => `/access-control/${space}`,
|
toCODESpaceAccessControl: ({ space }) => `/access-control/${space}`,
|
||||||
toCODESpaceSettings: ({ space, settingSection }) =>
|
toCODESpaceSettings: ({ space, settingSection, ruleId, settingSectionMode }) =>
|
||||||
`/settings/${space}/project${settingSection ? '/' + settingSection : ''}`,
|
`/settings/${space}/project${settingSection ? '/' + settingSection : ''}${ruleId ? '/' + ruleId : ''}${
|
||||||
|
settingSectionMode ? '/' + settingSectionMode : ''
|
||||||
|
}`,
|
||||||
|
toCODEManageRepositories: ({ space, settingSection, ruleId, settingSectionMode }) =>
|
||||||
|
`/${space}/manage-repositories${settingSection ? '/' + settingSection : ''}${ruleId ? '/' + ruleId : ''}${
|
||||||
|
settingSectionMode ? '/' + settingSectionMode : ''
|
||||||
|
}`,
|
||||||
toCODEPipelines: ({ repoPath }) => `/${repoPath}/pipelines`,
|
toCODEPipelines: ({ repoPath }) => `/${repoPath}/pipelines`,
|
||||||
toCODEPipelineEdit: ({ repoPath, pipeline }) => `/${repoPath}/pipelines/${pipeline}/edit`,
|
toCODEPipelineEdit: ({ repoPath, pipeline }) => `/${repoPath}/pipelines/${pipeline}/edit`,
|
||||||
toCODEPipelineSettings: ({ repoPath, pipeline }) => `/${repoPath}/pipelines/${pipeline}/triggers`,
|
toCODEPipelineSettings: ({ repoPath, pipeline }) => `/${repoPath}/pipelines/${pipeline}/triggers`,
|
||||||
@ -160,7 +170,6 @@ export const routes: CODERoutes = {
|
|||||||
toCODECompare: ({ repoPath, diffRefs }) => `/${repoPath}/pulls/compare/${diffRefs}`,
|
toCODECompare: ({ repoPath, diffRefs }) => `/${repoPath}/pulls/compare/${diffRefs}`,
|
||||||
toCODEBranches: ({ repoPath }) => `/${repoPath}/branches`,
|
toCODEBranches: ({ repoPath }) => `/${repoPath}/branches`,
|
||||||
toCODETags: ({ repoPath }) => `/${repoPath}/tags`,
|
toCODETags: ({ repoPath }) => `/${repoPath}/tags`,
|
||||||
toCODESpaceLabels: ({ space }) => `/${space}/labels`,
|
|
||||||
toCODESettings: ({ repoPath, settingSection, ruleId, settingSectionMode }) =>
|
toCODESettings: ({ repoPath, settingSection, ruleId, settingSectionMode }) =>
|
||||||
`/${repoPath}/settings${settingSection ? '/' + settingSection : ''}${ruleId ? '/' + ruleId : ''}${
|
`/${repoPath}/settings${settingSection ? '/' + settingSection : ''}${ruleId ? '/' + ruleId : ''}${
|
||||||
settingSectionMode ? '/' + settingSectionMode : ''
|
settingSectionMode ? '/' + settingSectionMode : ''
|
||||||
|
@ -55,7 +55,7 @@ import PipelineSettings from 'components/PipelineSettings/PipelineSettings'
|
|||||||
import GitspaceDetails from 'cde-gitness/pages/GitspaceDetails/GitspaceDetails'
|
import GitspaceDetails from 'cde-gitness/pages/GitspaceDetails/GitspaceDetails'
|
||||||
import GitspaceListing from 'cde-gitness/pages/GitspaceListing/GitspaceListing'
|
import GitspaceListing from 'cde-gitness/pages/GitspaceListing/GitspaceListing'
|
||||||
import GitspaceCreate from 'cde-gitness/pages/GitspaceCreate/GitspaceCreate'
|
import GitspaceCreate from 'cde-gitness/pages/GitspaceCreate/GitspaceCreate'
|
||||||
import ManageLabels from 'pages/ManageSpace/ManageLabels/ManageLabels'
|
import ManageRepositories from 'pages/ManageSpace/ManageRepositories/ManageRepositories'
|
||||||
|
|
||||||
const ArApp = lazy(() => import('@ar/gitness/ArApp'))
|
const ArApp = lazy(() => import('@ar/gitness/ArApp'))
|
||||||
|
|
||||||
@ -87,6 +87,17 @@ export const RouteDestinations: React.FC = React.memo(function RouteDestinations
|
|||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={[
|
path={[
|
||||||
|
routes.toCODESpaceSettings({
|
||||||
|
space: pathProps.space,
|
||||||
|
settingSection: pathProps.settingSection,
|
||||||
|
settingSectionMode: pathProps.settingSectionMode,
|
||||||
|
ruleId: pathProps.ruleId
|
||||||
|
}),
|
||||||
|
routes.toCODESpaceSettings({
|
||||||
|
space: pathProps.space,
|
||||||
|
settingSection: pathProps.settingSection,
|
||||||
|
settingSectionMode: pathProps.settingSectionMode
|
||||||
|
}),
|
||||||
routes.toCODESpaceSettings({ space: pathProps.space, settingSection: pathProps.settingSection }),
|
routes.toCODESpaceSettings({ space: pathProps.space, settingSection: pathProps.settingSection }),
|
||||||
routes.toCODESpaceSettings({ space: pathProps.space })
|
routes.toCODESpaceSettings({ space: pathProps.space })
|
||||||
]}
|
]}
|
||||||
@ -394,9 +405,26 @@ export const RouteDestinations: React.FC = React.memo(function RouteDestinations
|
|||||||
</LayoutWithSideNav>
|
</LayoutWithSideNav>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={routes.toCODESpaceLabels({ space: pathProps.space })} exact>
|
<Route
|
||||||
<LayoutWithSideNav title={getString('labels.labels')}>
|
path={[
|
||||||
<ManageLabels />
|
routes.toCODEManageRepositories({
|
||||||
|
space: pathProps.space,
|
||||||
|
settingSection: pathProps.settingSection,
|
||||||
|
settingSectionMode: pathProps.settingSectionMode,
|
||||||
|
ruleId: pathProps.ruleId
|
||||||
|
}),
|
||||||
|
routes.toCODEManageRepositories({
|
||||||
|
space: pathProps.space,
|
||||||
|
settingSection: pathProps.settingSection,
|
||||||
|
settingSectionMode: pathProps.settingSectionMode
|
||||||
|
}),
|
||||||
|
|
||||||
|
routes.toCODEManageRepositories({ space: pathProps.space, settingSection: pathProps.settingSection }),
|
||||||
|
routes.toCODEManageRepositories({ space: pathProps.space })
|
||||||
|
]}
|
||||||
|
exact>
|
||||||
|
<LayoutWithSideNav title={getString('pageTitle.repositorySettings')}>
|
||||||
|
<ManageRepositories />
|
||||||
</LayoutWithSideNav>
|
</LayoutWithSideNav>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
@ -38,9 +38,12 @@ import { useGet, useMutate } from 'restful-react'
|
|||||||
import { BranchTargetType, MergeStrategy, SettingTypeMode, SettingsTab, branchTargetOptions } from 'utils/GitUtils'
|
import { BranchTargetType, MergeStrategy, SettingTypeMode, SettingsTab, branchTargetOptions } from 'utils/GitUtils'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import {
|
import {
|
||||||
|
LabelsPageScope,
|
||||||
REGEX_VALID_REPO_NAME,
|
REGEX_VALID_REPO_NAME,
|
||||||
RulesFormPayload,
|
RulesFormPayload,
|
||||||
|
getEditPermissionRequestFromScope,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
|
getScopeData,
|
||||||
permissionProps,
|
permissionProps,
|
||||||
rulesFormInitialPayload
|
rulesFormInitialPayload
|
||||||
} from 'utils/Utils'
|
} from 'utils/Utils'
|
||||||
@ -55,6 +58,7 @@ import type {
|
|||||||
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
|
import { getConfig } from 'services/config'
|
||||||
import ProtectionRulesForm from './ProtectionRulesForm/ProtectionRulesForm'
|
import ProtectionRulesForm from './ProtectionRulesForm/ProtectionRulesForm'
|
||||||
import Include from '../../../icons/Include.svg?url'
|
import Include from '../../../icons/Include.svg?url'
|
||||||
import Exclude from '../../../icons/Exclude.svg?url'
|
import Exclude from '../../../icons/Exclude.svg?url'
|
||||||
@ -62,33 +66,50 @@ import BypassList from './BypassList'
|
|||||||
import css from './BranchProtectionForm.module.scss'
|
import css from './BranchProtectionForm.module.scss'
|
||||||
|
|
||||||
const BranchProtectionForm = (props: {
|
const BranchProtectionForm = (props: {
|
||||||
ruleUid: string
|
currentRule?: OpenapiRule
|
||||||
editMode: boolean
|
editMode: boolean
|
||||||
repoMetadata?: RepoRepositoryOutput | undefined
|
repoMetadata?: RepoRepositoryOutput | undefined
|
||||||
refetchRules: () => void
|
refetchRules: () => void
|
||||||
settingSectionMode: SettingTypeMode
|
settingSectionMode: SettingTypeMode
|
||||||
|
currentPageScope: LabelsPageScope
|
||||||
}) => {
|
}) => {
|
||||||
const { routes, routingId, standalone, hooks } = useAppContext()
|
const { routes, routingId, standalone, hooks } = useAppContext()
|
||||||
|
|
||||||
const { ruleId } = useGetRepositoryMetadata()
|
const { ruleId } = useGetRepositoryMetadata()
|
||||||
const { showError, showSuccess } = useToaster()
|
const { showError, showSuccess } = useToaster()
|
||||||
const { editMode = false, repoMetadata, ruleUid, refetchRules, settingSectionMode } = props
|
const space = useGetSpaceParam()
|
||||||
|
const { editMode = false, repoMetadata, currentRule, refetchRules, settingSectionMode, currentPageScope } = props
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const { data: rule } = useGet<OpenapiRule>({
|
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/rules/${ruleId}`,
|
|
||||||
lazy: !repoMetadata && !ruleId
|
|
||||||
})
|
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [searchStatusTerm, setSearchStatusTerm] = useState('')
|
const [searchStatusTerm, setSearchStatusTerm] = useState('')
|
||||||
|
const { scopeRef } = currentRule?.scope ? getScopeData(space, currentRule?.scope, standalone) : { scopeRef: space }
|
||||||
|
|
||||||
|
const getUpdateRulePath = () =>
|
||||||
|
currentRule?.scope === 0 && repoMetadata
|
||||||
|
? `/repos/${repoMetadata?.path}/+/rules/${encodeURIComponent(ruleId)}`
|
||||||
|
: `/spaces/${scopeRef}/+/rules/${encodeURIComponent(ruleId)}`
|
||||||
|
|
||||||
|
const getCreateRulePath = () =>
|
||||||
|
currentPageScope === LabelsPageScope.REPOSITORY
|
||||||
|
? `/repos/${repoMetadata?.path}/+/rules`
|
||||||
|
: `/spaces/${space}/+/rules`
|
||||||
|
|
||||||
|
const { data: rule } = useGet<OpenapiRule>({
|
||||||
|
base: getConfig('code/api/v1'),
|
||||||
|
path: getUpdateRulePath(),
|
||||||
|
lazy: !ruleId
|
||||||
|
})
|
||||||
|
|
||||||
const { mutate } = useMutate({
|
const { mutate } = useMutate({
|
||||||
verb: 'POST',
|
verb: 'POST',
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/rules/`
|
base: getConfig('code/api/v1'),
|
||||||
|
path: getCreateRulePath()
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutate: updateRule } = useMutate({
|
const { mutate: updateRule } = useMutate({
|
||||||
verb: 'PATCH',
|
verb: 'PATCH',
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/rules/${ruleId}`
|
base: getConfig('code/api/v1'),
|
||||||
|
path: getUpdateRulePath()
|
||||||
})
|
})
|
||||||
const { data: users } = useGet<TypesPrincipalInfo[]>({
|
const { data: users } = useGet<TypesPrincipalInfo[]>({
|
||||||
path: `/api/v1/principals`,
|
path: `/api/v1/principals`,
|
||||||
@ -111,10 +132,19 @@ const BranchProtectionForm = (props: {
|
|||||||
const usersArrayCurr = transformUserArray?.map(user => `${user.id} ${user.display_name}`)
|
const usersArrayCurr = transformUserArray?.map(user => `${user.id} ${user.display_name}`)
|
||||||
const [userArrayState, setUserArrayState] = useState<string[]>(usersArrayCurr)
|
const [userArrayState, setUserArrayState] = useState<string[]>(usersArrayCurr)
|
||||||
|
|
||||||
|
const getUpdateChecksPath = () =>
|
||||||
|
currentRule?.scope === 0 && repoMetadata
|
||||||
|
? `/repos/${repoMetadata?.path}/+/checks/recent`
|
||||||
|
: `/spaces/${scopeRef}/+/checks/recent`
|
||||||
|
|
||||||
const { data: statuses } = useGet<string[]>({
|
const { data: statuses } = useGet<string[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/checks/recent`,
|
base: getConfig('code/api/v1'),
|
||||||
|
path: getUpdateChecksPath(),
|
||||||
queryParams: {
|
queryParams: {
|
||||||
query: searchStatusTerm
|
query: searchStatusTerm,
|
||||||
|
...(!repoMetadata && {
|
||||||
|
recursive: true
|
||||||
|
})
|
||||||
},
|
},
|
||||||
debounce: 500
|
debounce: 500
|
||||||
})
|
})
|
||||||
@ -141,10 +171,20 @@ const BranchProtectionForm = (props: {
|
|||||||
showSuccess(successMessage)
|
showSuccess(successMessage)
|
||||||
resetForm()
|
resetForm()
|
||||||
history.push(
|
history.push(
|
||||||
routes.toCODESettings({
|
repoMetadata
|
||||||
|
? routes.toCODESettings({
|
||||||
repoPath: repoMetadata?.path as string,
|
repoPath: repoMetadata?.path as string,
|
||||||
settingSection: SettingsTab.branchProtection
|
settingSection: SettingsTab.branchProtection
|
||||||
})
|
})
|
||||||
|
: standalone
|
||||||
|
? routes.toCODESpaceSettings({
|
||||||
|
space,
|
||||||
|
settingSection: SettingsTab.branchProtection
|
||||||
|
})
|
||||||
|
: routes.toCODEManageRepositories({
|
||||||
|
space,
|
||||||
|
settingSection: SettingsTab.branchProtection
|
||||||
|
})
|
||||||
)
|
)
|
||||||
refetchRules?.()
|
refetchRules?.()
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
@ -223,17 +263,11 @@ const BranchProtectionForm = (props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return rulesFormInitialPayload // eslint-disable-next-line react-hooks/exhaustive-deps
|
return rulesFormInitialPayload // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [editMode, rule, ruleUid, users])
|
}, [editMode, rule, currentRule, users])
|
||||||
const space = useGetSpaceParam()
|
|
||||||
const permPushResult = hooks?.usePermissionTranslate?.(
|
const permPushResult = hooks?.usePermissionTranslate(
|
||||||
{
|
getEditPermissionRequestFromScope(space, currentRule?.scope ?? 0, repoMetadata),
|
||||||
resource: {
|
[space, currentRule?.scope, repoMetadata]
|
||||||
resourceType: 'CODE_REPOSITORY',
|
|
||||||
resourceIdentifier: repoMetadata?.identifier as string
|
|
||||||
},
|
|
||||||
permissions: ['code_repo_edit']
|
|
||||||
},
|
|
||||||
[space]
|
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<Formik<RulesFormPayload>
|
<Formik<RulesFormPayload>
|
||||||
@ -392,7 +426,7 @@ const BranchProtectionForm = (props: {
|
|||||||
flex={{ align: 'center-center' }}
|
flex={{ align: 'center-center' }}
|
||||||
padding={{ top: 'xxlarge', left: 'small' }}>
|
padding={{ top: 'xxlarge', left: 'small' }}>
|
||||||
<SplitButton
|
<SplitButton
|
||||||
// className={css.buttonContainer}
|
className={css.buttonContainer}
|
||||||
variation={ButtonVariation.TERTIARY}
|
variation={ButtonVariation.TERTIARY}
|
||||||
text={
|
text={
|
||||||
<Container flex={{ alignItems: 'center' }}>
|
<Container flex={{ alignItems: 'center' }}>
|
||||||
|
@ -59,3 +59,10 @@
|
|||||||
.cancelButton {
|
.cancelButton {
|
||||||
margin-left: var(--spacing-small) !important;
|
margin-left: var(--spacing-small) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scopeCheckbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: var(--spacing-3) !important;
|
||||||
|
padding-left: var(--spacing-3) !important;
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ export declare const cancelButton: string
|
|||||||
export declare const main: string
|
export declare const main: string
|
||||||
export declare const noData: string
|
export declare const noData: string
|
||||||
export declare const row: string
|
export declare const row: string
|
||||||
|
export declare const scopeCheckbox: string
|
||||||
export declare const table: string
|
export declare const table: string
|
||||||
export declare const title: string
|
export declare const title: string
|
||||||
export declare const toggle: string
|
export declare const toggle: string
|
||||||
|
@ -15,19 +15,23 @@
|
|||||||
*/
|
*/
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Container, Layout, FlexExpander, ButtonVariation, Button } from '@harnessio/uicore'
|
import { Container, Layout, FlexExpander, ButtonVariation, Button, Checkbox } from '@harnessio/uicore'
|
||||||
|
import { Render } from 'react-jsx-match'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { CodeIcon, GitInfoProps, SettingTypeMode } from 'utils/GitUtils'
|
import { CodeIcon, GitInfoProps, SettingTypeMode } from 'utils/GitUtils'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
|
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
|
||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
import { permissionProps } from 'utils/Utils'
|
import { getEditPermissionRequestFromIdentifier, permissionProps } from 'utils/Utils'
|
||||||
import css from './BranchProtectionHeader.module.scss'
|
import css from './BranchProtectionHeader.module.scss'
|
||||||
const BranchProtectionHeader = ({
|
const BranchProtectionHeader = ({
|
||||||
repoMetadata,
|
repoMetadata,
|
||||||
loading,
|
loading,
|
||||||
onSearchTermChanged,
|
onSearchTermChanged,
|
||||||
activeTab
|
activeTab,
|
||||||
|
showParentScopeFilter,
|
||||||
|
inheritRules,
|
||||||
|
setInheritRules
|
||||||
}: BranchProtectionHeaderProps) => {
|
}: BranchProtectionHeaderProps) => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
@ -37,17 +41,10 @@ const BranchProtectionHeader = ({
|
|||||||
|
|
||||||
const space = useGetSpaceParam()
|
const space = useGetSpaceParam()
|
||||||
|
|
||||||
const permPushResult = hooks?.usePermissionTranslate?.(
|
const permPushResult = hooks?.usePermissionTranslate(getEditPermissionRequestFromIdentifier(space, repoMetadata), [
|
||||||
{
|
space,
|
||||||
resource: {
|
repoMetadata
|
||||||
resourceType: 'CODE_REPOSITORY',
|
])
|
||||||
resourceIdentifier: repoMetadata?.identifier as string
|
|
||||||
},
|
|
||||||
permissions: ['code_repo_edit']
|
|
||||||
},
|
|
||||||
[space]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={css.main} padding="xlarge">
|
<Container className={css.main} padding="xlarge">
|
||||||
<Layout.Horizontal spacing="medium">
|
<Layout.Horizontal spacing="medium">
|
||||||
@ -56,16 +53,42 @@ const BranchProtectionHeader = ({
|
|||||||
text={getString('branchProtection.newRule')}
|
text={getString('branchProtection.newRule')}
|
||||||
icon={CodeIcon.Add}
|
icon={CodeIcon.Add}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
history.push(
|
repoMetadata
|
||||||
|
? history.push(
|
||||||
routes.toCODESettings({
|
routes.toCODESettings({
|
||||||
repoPath: repoMetadata?.path as string,
|
repoPath: repoMetadata?.path as string,
|
||||||
settingSection: activeTab,
|
settingSection: activeTab,
|
||||||
settingSectionMode: SettingTypeMode.NEW
|
settingSectionMode: SettingTypeMode.NEW
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
: standalone
|
||||||
|
? history.push(
|
||||||
|
routes.toCODESpaceSettings({
|
||||||
|
space,
|
||||||
|
settingSection: activeTab,
|
||||||
|
settingSectionMode: SettingTypeMode.NEW
|
||||||
|
})
|
||||||
|
)
|
||||||
|
: history.push(
|
||||||
|
routes.toCODEManageRepositories({
|
||||||
|
space,
|
||||||
|
settingSection: activeTab,
|
||||||
|
settingSectionMode: SettingTypeMode.NEW
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
{...permissionProps(permPushResult, standalone)}
|
{...permissionProps(permPushResult, standalone)}
|
||||||
/>
|
/>
|
||||||
|
<Render when={showParentScopeFilter}>
|
||||||
|
<Checkbox
|
||||||
|
className={css.scopeCheckbox}
|
||||||
|
label={getString('branchProtection.showRulesScope')}
|
||||||
|
checked={inheritRules}
|
||||||
|
onChange={event => {
|
||||||
|
setInheritRules(event.currentTarget.checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Render>
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
<SearchInputWithSpinner
|
<SearchInputWithSpinner
|
||||||
spinnerPosition="right"
|
spinnerPosition="right"
|
||||||
@ -83,8 +106,11 @@ const BranchProtectionHeader = ({
|
|||||||
|
|
||||||
export default BranchProtectionHeader
|
export default BranchProtectionHeader
|
||||||
|
|
||||||
interface BranchProtectionHeaderProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
interface BranchProtectionHeaderProps extends Partial<Pick<GitInfoProps, 'repoMetadata'>> {
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
activeTab?: string
|
activeTab?: string
|
||||||
|
showParentScopeFilter: boolean
|
||||||
|
inheritRules: boolean
|
||||||
|
setInheritRules: (value: boolean) => void
|
||||||
onSearchTermChanged: (searchTerm: string) => void
|
onSearchTermChanged: (searchTerm: string) => void
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useMemo, useState } from 'react'
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
Layout,
|
Layout,
|
||||||
@ -35,8 +35,8 @@ import type { CellProps, Column } from 'react-table'
|
|||||||
import { useGet, useMutate } from 'restful-react'
|
import { useGet, useMutate } from 'restful-react'
|
||||||
import { FontVariation } from '@harnessio/design-system'
|
import { FontVariation } from '@harnessio/design-system'
|
||||||
import { Position } from '@blueprintjs/core'
|
import { Position } from '@blueprintjs/core'
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory, useParams } from 'react-router-dom'
|
||||||
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
import { Icon } from '@harnessio/icons'
|
||||||
import { useQueryParams } from 'hooks/useQueryParams'
|
import { useQueryParams } from 'hooks/useQueryParams'
|
||||||
import { usePageIndex } from 'hooks/usePageIndex'
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
import {
|
import {
|
||||||
@ -47,7 +47,12 @@ import {
|
|||||||
Rule,
|
Rule,
|
||||||
RuleFields,
|
RuleFields,
|
||||||
BranchProtectionRulesMapType,
|
BranchProtectionRulesMapType,
|
||||||
createRuleFieldsMap
|
createRuleFieldsMap,
|
||||||
|
LabelsPageScope,
|
||||||
|
getScopeData,
|
||||||
|
getScopeIcon,
|
||||||
|
getEditPermissionRequestFromScope,
|
||||||
|
getEditPermissionRequestFromIdentifier
|
||||||
} from 'utils/Utils'
|
} from 'utils/Utils'
|
||||||
import { SettingTypeMode } from 'utils/GitUtils'
|
import { SettingTypeMode } from 'utils/GitUtils'
|
||||||
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
@ -56,46 +61,70 @@ import { useStrings } from 'framework/strings'
|
|||||||
|
|
||||||
import { useConfirmAct } from 'hooks/useConfirmAction'
|
import { useConfirmAct } from 'hooks/useConfirmAction'
|
||||||
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
||||||
import type { OpenapiRule, ProtectionPattern } from 'services/code'
|
import type { OpenapiRule, ProtectionPattern, RepoRepositoryOutput } from 'services/code'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||||
|
import type { CODEProps } from 'RouteDefinitions'
|
||||||
|
import { getConfig } from 'services/config'
|
||||||
import Include from '../../icons/Include.svg?url'
|
import Include from '../../icons/Include.svg?url'
|
||||||
import Exclude from '../../icons/Exclude.svg?url'
|
import Exclude from '../../icons/Exclude.svg?url'
|
||||||
import BranchProtectionForm from './BranchProtectionForm/BranchProtectionForm'
|
import BranchProtectionForm from './BranchProtectionForm/BranchProtectionForm'
|
||||||
import BranchProtectionHeader from './BranchProtectionHeader/BranchProtectionHeader'
|
import BranchProtectionHeader from './BranchProtectionHeader/BranchProtectionHeader'
|
||||||
import css from './BranchProtectionListing.module.scss'
|
import css from './BranchProtectionListing.module.scss'
|
||||||
|
|
||||||
const BranchProtectionListing = (props: { activeTab: string }) => {
|
const BranchProtectionListing = (props: {
|
||||||
const { activeTab } = props
|
activeTab: string
|
||||||
|
repoMetadata?: RepoRepositoryOutput
|
||||||
|
currentPageScope: LabelsPageScope
|
||||||
|
}) => {
|
||||||
|
const { activeTab, repoMetadata, currentPageScope } = props
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const { showError, showSuccess } = useToaster()
|
const { showError, showSuccess } = useToaster()
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const { routes } = useAppContext()
|
const { routes, standalone, hooks } = useAppContext()
|
||||||
const pageBrowser = useQueryParams<PageBrowserProps>()
|
const pageBrowser = useQueryParams<PageBrowserProps>()
|
||||||
const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1
|
const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1
|
||||||
const [page, setPage] = usePageIndex(pageInit)
|
const [page, setPage] = usePageIndex(pageInit)
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [curRuleName, setCurRuleName] = useState('')
|
const [currentRule, setCurrentRule] = useState<OpenapiRule>()
|
||||||
const { repoMetadata, settingSection, ruleId, settingSectionMode } = useGetRepositoryMetadata()
|
const { settingSection, ruleId, settingSectionMode } = useParams<CODEProps>()
|
||||||
const newRule = settingSection && settingSectionMode === SettingTypeMode.NEW
|
const newRule = settingSection && settingSectionMode === SettingTypeMode.NEW
|
||||||
const editRule = settingSection !== '' && ruleId !== '' && settingSectionMode === SettingTypeMode.EDIT
|
const editRule = settingSection !== '' && ruleId !== '' && settingSectionMode === SettingTypeMode.EDIT
|
||||||
|
const [showParentScopeFilter, setShowParentScopeFilter] = useState<boolean>(true)
|
||||||
|
const [inheritRules, setInheritRules] = useState<boolean>(false)
|
||||||
|
const space = useGetSpaceParam()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentPageScope) {
|
||||||
|
if (currentPageScope === LabelsPageScope.ACCOUNT) setShowParentScopeFilter(false)
|
||||||
|
else if (currentPageScope === LabelsPageScope.SPACE) setShowParentScopeFilter(false)
|
||||||
|
}
|
||||||
|
}, [currentPageScope, standalone])
|
||||||
|
|
||||||
|
const getRulesPath = () =>
|
||||||
|
currentPageScope === LabelsPageScope.REPOSITORY
|
||||||
|
? `/repos/${repoMetadata?.path}/+/rules`
|
||||||
|
: `/spaces/${space}/+/rules`
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: rules,
|
data: rules,
|
||||||
refetch: refetchRules,
|
refetch: refetchRules,
|
||||||
loading: loadingRules,
|
loading: loadingRules,
|
||||||
response
|
response
|
||||||
} = useGet<OpenapiRule[]>({
|
} = useGet<OpenapiRule[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/rules`,
|
base: getConfig('code/api/v1'),
|
||||||
|
path: getRulesPath(),
|
||||||
queryParams: {
|
queryParams: {
|
||||||
limit: LIST_FETCHING_LIMIT,
|
limit: LIST_FETCHING_LIMIT,
|
||||||
|
inherited: inheritRules,
|
||||||
page,
|
page,
|
||||||
sort: 'date',
|
sort: 'date',
|
||||||
order: 'desc',
|
order: 'desc',
|
||||||
query: searchTerm
|
query: searchTerm
|
||||||
},
|
},
|
||||||
debounce: 500,
|
debounce: 500,
|
||||||
lazy: !repoMetadata || !!editRule
|
lazy: !!editRule
|
||||||
})
|
})
|
||||||
|
|
||||||
const branchProtectionRules = {
|
const branchProtectionRules = {
|
||||||
@ -187,24 +216,83 @@ const BranchProtectionListing = (props: { activeTab: string }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function navigateToSettings({
|
||||||
|
repoMetadata: metaData,
|
||||||
|
standalone: isStandalone,
|
||||||
|
space: currentSpace,
|
||||||
|
scope: currentScope,
|
||||||
|
settingSection: section,
|
||||||
|
settingSectionMode: sectionMode,
|
||||||
|
ruleId: id
|
||||||
|
}: {
|
||||||
|
repoMetadata?: RepoRepositoryOutput
|
||||||
|
standalone?: boolean
|
||||||
|
space: string
|
||||||
|
scope?: number
|
||||||
|
settingSection?: string
|
||||||
|
settingSectionMode?: string
|
||||||
|
ruleId?: string
|
||||||
|
}) {
|
||||||
|
const { scopeRef } = currentScope
|
||||||
|
? getScopeData(currentSpace, currentScope, isStandalone ?? false)
|
||||||
|
: { scopeRef: currentSpace }
|
||||||
|
|
||||||
|
if (metaData && currentScope === 0) {
|
||||||
|
history.push(
|
||||||
|
routes.toCODESettings({
|
||||||
|
repoPath: metaData.path as string,
|
||||||
|
settingSection: section,
|
||||||
|
settingSectionMode: sectionMode,
|
||||||
|
ruleId: id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else if (isStandalone) {
|
||||||
|
history.push(
|
||||||
|
routes.toCODESpaceSettings({
|
||||||
|
space: currentSpace,
|
||||||
|
settingSection: section,
|
||||||
|
settingSectionMode: sectionMode,
|
||||||
|
ruleId: id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
history.push(
|
||||||
|
routes.toCODEManageRepositories({
|
||||||
|
space: scopeRef,
|
||||||
|
settingSection: section,
|
||||||
|
settingSectionMode: sectionMode,
|
||||||
|
ruleId: id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const columns: Column<OpenapiRule>[] = useMemo(
|
const columns: Column<OpenapiRule>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
id: 'title',
|
id: 'title',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
Cell: ({ row }: CellProps<OpenapiRule>) => {
|
Cell: ({ row }: CellProps<OpenapiRule>) => {
|
||||||
|
const { scopeRef } = getScopeData(space, row.original?.scope ?? 1, standalone)
|
||||||
|
const getRuleIDPath = () =>
|
||||||
|
row.original?.scope === 0 && repoMetadata
|
||||||
|
? `/repos/${repoMetadata?.path}/+/rules/${encodeURIComponent(row.original?.identifier as string)}`
|
||||||
|
: `/spaces/${scopeRef}/+/rules/${encodeURIComponent(row.original?.identifier as string)}`
|
||||||
|
|
||||||
const [checked, setChecked] = useState<boolean>(
|
const [checked, setChecked] = useState<boolean>(
|
||||||
row.original.state === 'active' || row.original.state === 'monitor' ? true : false
|
row.original.state === 'active' || row.original.state === 'monitor' ? true : false
|
||||||
)
|
)
|
||||||
|
|
||||||
const { mutate } = useMutate<OpenapiRule>({
|
const { mutate: toggleRule } = useMutate<OpenapiRule>({
|
||||||
verb: 'PATCH',
|
verb: 'PATCH',
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/rules/${row.original?.identifier}`
|
base: getConfig('code/api/v1'),
|
||||||
|
path: getRuleIDPath()
|
||||||
})
|
})
|
||||||
const [popoverDialogOpen, setPopoverDialogOpen] = useState(false)
|
const [popoverDialogOpen, setPopoverDialogOpen] = useState(false)
|
||||||
const { mutate: deleteRule } = useMutate({
|
const { mutate: deleteRule } = useMutate({
|
||||||
verb: 'DELETE',
|
verb: 'DELETE',
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/rules/${row.original.identifier}`
|
base: getConfig('code/api/v1'),
|
||||||
|
path: getRuleIDPath()
|
||||||
})
|
})
|
||||||
const confirmDelete = useConfirmAct()
|
const confirmDelete = useConfirmAct()
|
||||||
const includeElements = (row.original?.pattern as ProtectionPattern)?.include?.map(
|
const includeElements = (row.original?.pattern as ProtectionPattern)?.include?.map(
|
||||||
@ -266,20 +354,13 @@ const BranchProtectionListing = (props: { activeTab: string }) => {
|
|||||||
|
|
||||||
const nonEmptyRules = checkAppliedRules(row.original.definition as Rule, branchProtectionRules)
|
const nonEmptyRules = checkAppliedRules(row.original.definition as Rule, branchProtectionRules)
|
||||||
|
|
||||||
const { hooks, standalone } = useAppContext()
|
const scope = row.original?.scope
|
||||||
|
const permPushResult = hooks?.usePermissionTranslate(
|
||||||
const space = useGetSpaceParam()
|
getEditPermissionRequestFromScope(space, scope ?? 0, repoMetadata),
|
||||||
|
[space, repoMetadata, scope]
|
||||||
const permPushResult = hooks?.usePermissionTranslate?.(
|
|
||||||
{
|
|
||||||
resource: {
|
|
||||||
resourceType: 'CODE_REPOSITORY',
|
|
||||||
resourceIdentifier: repoMetadata?.identifier as string
|
|
||||||
},
|
|
||||||
permissions: ['code_repo_edit']
|
|
||||||
},
|
|
||||||
[space]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const scopeIcon = getScopeIcon(row.original?.scope, standalone)
|
||||||
return (
|
return (
|
||||||
<Layout.Horizontal spacing="medium" padding={{ left: 'medium' }}>
|
<Layout.Horizontal spacing="medium" padding={{ left: 'medium' }}>
|
||||||
<Container onClick={Utils.stopEvent}>
|
<Container onClick={Utils.stopEvent}>
|
||||||
@ -313,7 +394,7 @@ const BranchProtectionListing = (props: { activeTab: string }) => {
|
|||||||
text={getString('confirm')}
|
text={getString('confirm')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const data = { state: checked ? 'disabled' : 'active' }
|
const data = { state: checked ? 'disabled' : 'active' }
|
||||||
mutate(data)
|
toggleRule(data)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showSuccess(getString('branchProtection.ruleUpdated'))
|
showSuccess(getString('branchProtection.ruleUpdated'))
|
||||||
})
|
})
|
||||||
@ -348,10 +429,12 @@ const BranchProtectionListing = (props: { activeTab: string }) => {
|
|||||||
<Container padding={{ left: 'small' }} style={{ flexGrow: 1 }}>
|
<Container padding={{ left: 'small' }} style={{ flexGrow: 1 }}>
|
||||||
<Layout.Horizontal spacing="small">
|
<Layout.Horizontal spacing="small">
|
||||||
<Layout.Vertical>
|
<Layout.Vertical>
|
||||||
<Text padding={{ right: 'small', top: 'xsmall' }} className={css.title}>
|
<Layout.Horizontal
|
||||||
{row.original.identifier}
|
padding={{ right: 'small', top: 'xsmall' }}
|
||||||
</Text>
|
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
|
||||||
|
{scopeIcon && <Icon padding={{ right: 'small' }} name={scopeIcon} size={16} />}
|
||||||
|
<Text className={css.title}>{row.original.identifier}</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
{!!row.original.description && (
|
{!!row.original.description && (
|
||||||
<Text
|
<Text
|
||||||
lineClamp={4}
|
lineClamp={4}
|
||||||
@ -374,14 +457,16 @@ const BranchProtectionListing = (props: { activeTab: string }) => {
|
|||||||
iconName: 'Edit',
|
iconName: 'Edit',
|
||||||
text: getString('branchProtection.editRule'),
|
text: getString('branchProtection.editRule'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
history.push(
|
setCurrentRule(row.original)
|
||||||
routes.toCODESettings({
|
navigateToSettings({
|
||||||
repoPath: repoMetadata?.path as string,
|
repoMetadata,
|
||||||
settingSection: settingSection,
|
standalone,
|
||||||
|
space,
|
||||||
|
scope,
|
||||||
|
settingSection,
|
||||||
settingSectionMode: SettingTypeMode.EDIT,
|
settingSectionMode: SettingTypeMode.EDIT,
|
||||||
ruleId: String(row.original.identifier)
|
ruleId: String(row.original.identifier)
|
||||||
})
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -457,40 +542,35 @@ const BranchProtectionListing = (props: { activeTab: string }) => {
|
|||||||
], // eslint-disable-next-line react-hooks/exhaustive-deps
|
], // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[history, getString, repoMetadata?.path, setPage, showError, showSuccess]
|
[history, getString, repoMetadata?.path, setPage, showError, showSuccess]
|
||||||
)
|
)
|
||||||
const { hooks, standalone } = useAppContext()
|
|
||||||
|
|
||||||
const space = useGetSpaceParam()
|
const permPushResult = hooks?.usePermissionTranslate(getEditPermissionRequestFromIdentifier(space, repoMetadata), [
|
||||||
|
space,
|
||||||
const permPushResult = hooks?.usePermissionTranslate?.(
|
repoMetadata
|
||||||
{
|
])
|
||||||
resource: {
|
|
||||||
resourceType: 'CODE_REPOSITORY',
|
|
||||||
resourceIdentifier: repoMetadata?.identifier as string
|
|
||||||
},
|
|
||||||
permissions: ['code_repo_edit']
|
|
||||||
},
|
|
||||||
[space]
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<LoadingSpinner visible={loadingRules} />
|
<LoadingSpinner visible={loadingRules} />
|
||||||
{repoMetadata && !newRule && !editRule && (
|
{!newRule && !editRule && (
|
||||||
<BranchProtectionHeader
|
<BranchProtectionHeader
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
|
showParentScopeFilter={showParentScopeFilter}
|
||||||
onSearchTermChanged={(value: React.SetStateAction<string>) => {
|
onSearchTermChanged={(value: React.SetStateAction<string>) => {
|
||||||
setSearchTerm(value)
|
setSearchTerm(value)
|
||||||
setPage(1)
|
setPage(1)
|
||||||
}}
|
}}
|
||||||
repoMetadata={repoMetadata}
|
inheritRules={inheritRules}
|
||||||
|
setInheritRules={setInheritRules}
|
||||||
|
{...(repoMetadata && { repoMetadata: repoMetadata })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{newRule || editRule ? (
|
{newRule || editRule ? (
|
||||||
<BranchProtectionForm
|
<BranchProtectionForm
|
||||||
editMode={editRule}
|
editMode={editRule}
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
ruleUid={curRuleName}
|
currentRule={currentRule}
|
||||||
refetchRules={refetchRules}
|
refetchRules={refetchRules}
|
||||||
settingSectionMode={settingSectionMode}
|
settingSectionMode={settingSectionMode}
|
||||||
|
currentPageScope={currentPageScope}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Container padding="xlarge">
|
<Container padding="xlarge">
|
||||||
@ -503,15 +583,16 @@ const BranchProtectionListing = (props: { activeTab: string }) => {
|
|||||||
data={rules}
|
data={rules}
|
||||||
getRowClassName={() => css.row}
|
getRowClassName={() => css.row}
|
||||||
onRowClick={row => {
|
onRowClick={row => {
|
||||||
setCurRuleName(row.identifier as string)
|
setCurrentRule(row)
|
||||||
history.push(
|
navigateToSettings({
|
||||||
routes.toCODESettings({
|
repoMetadata,
|
||||||
repoPath: repoMetadata?.path as string,
|
standalone,
|
||||||
settingSection: settingSection,
|
space,
|
||||||
|
scope: row.scope,
|
||||||
|
settingSection,
|
||||||
settingSectionMode: SettingTypeMode.EDIT,
|
settingSectionMode: SettingTypeMode.EDIT,
|
||||||
ruleId: String(row.identifier)
|
ruleId: String(row.identifier)
|
||||||
})
|
})
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -525,13 +606,13 @@ const BranchProtectionListing = (props: { activeTab: string }) => {
|
|||||||
message={getString('branchProtection.ruleEmpty')}
|
message={getString('branchProtection.ruleEmpty')}
|
||||||
buttonText={getString('branchProtection.newRule')}
|
buttonText={getString('branchProtection.newRule')}
|
||||||
onButtonClick={() => {
|
onButtonClick={() => {
|
||||||
history.push(
|
navigateToSettings({
|
||||||
routes.toCODESettings({
|
repoMetadata,
|
||||||
repoPath: repoMetadata?.path as string,
|
standalone,
|
||||||
settingSection: activeTab,
|
space,
|
||||||
|
settingSection,
|
||||||
settingSectionMode: SettingTypeMode.NEW
|
settingSectionMode: SettingTypeMode.NEW
|
||||||
})
|
})
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
permissionProp={permissionProps(permPushResult, standalone)}
|
permissionProp={permissionProps(permPushResult, standalone)}
|
||||||
/>
|
/>
|
||||||
|
@ -129,6 +129,7 @@ export interface StringsMap {
|
|||||||
'branchProtection.ruleEmpty': string
|
'branchProtection.ruleEmpty': string
|
||||||
'branchProtection.ruleUpdated': string
|
'branchProtection.ruleUpdated': string
|
||||||
'branchProtection.saveRule': string
|
'branchProtection.saveRule': string
|
||||||
|
'branchProtection.showRulesScope': string
|
||||||
'branchProtection.statusCheck': string
|
'branchProtection.statusCheck': string
|
||||||
'branchProtection.targetBranches': string
|
'branchProtection.targetBranches': string
|
||||||
'branchProtection.targetPatternHint': string
|
'branchProtection.targetPatternHint': string
|
||||||
@ -592,6 +593,7 @@ export interface StringsMap {
|
|||||||
makeRequired: string
|
makeRequired: string
|
||||||
manageApiToken: string
|
manageApiToken: string
|
||||||
manageCredText: string
|
manageCredText: string
|
||||||
|
manageRepositories: string
|
||||||
manageRepository: string
|
manageRepository: string
|
||||||
markAsDraft: string
|
markAsDraft: string
|
||||||
matchPassword: string
|
matchPassword: string
|
||||||
|
@ -31,6 +31,7 @@ commitChanges: Commit changes
|
|||||||
pullRequests: Pull Requests
|
pullRequests: Pull Requests
|
||||||
settings: Settings
|
settings: Settings
|
||||||
manageRepository: Manage Repository
|
manageRepository: Manage Repository
|
||||||
|
manageRepositories: Manage Repositories
|
||||||
newFile: New File
|
newFile: New File
|
||||||
editFile: Edit File
|
editFile: Edit File
|
||||||
prev: Prev
|
prev: Prev
|
||||||
@ -990,7 +991,7 @@ branchProtection:
|
|||||||
targetPatternHint: Match branches using globstar patterns (e.g. "golden", "feature-*", "releases/**")
|
targetPatternHint: Match branches using globstar patterns (e.g. "golden", "feature-*", "releases/**")
|
||||||
defaultBranch: Default branch
|
defaultBranch: Default branch
|
||||||
bypassList: Bypass List
|
bypassList: Bypass List
|
||||||
newRule: New branch rule
|
newRule: New Branch Rule
|
||||||
allRepoOwners: Allow users with edit permission on the repository to bypass
|
allRepoOwners: Allow users with edit permission on the repository to bypass
|
||||||
protectionSelectAll: 'Rules: Select all that apply'
|
protectionSelectAll: 'Rules: Select all that apply'
|
||||||
requireMinReviewersTitle: Require a minimum number of reviewers
|
requireMinReviewersTitle: Require a minimum number of reviewers
|
||||||
@ -1025,6 +1026,7 @@ branchProtection:
|
|||||||
deleteRule: Delete Rule
|
deleteRule: Delete Rule
|
||||||
ruleDeleted: Rule Deleted
|
ruleDeleted: Rule Deleted
|
||||||
ruleEmpty: There are no rules in your repo. Click the button below to create a rule.
|
ruleEmpty: There are no rules in your repo. Click the button below to create a rule.
|
||||||
|
showRulesScope: Show rules from parent scopes
|
||||||
createRule: Create rule
|
createRule: Create rule
|
||||||
deleteProtectionRule: Delete protection rule
|
deleteProtectionRule: Delete protection rule
|
||||||
deleteText: "Are you sure to delete the rule, '{{rule}}'?"
|
deleteText: "Are you sure to delete the rule, '{{rule}}'?"
|
||||||
|
@ -37,7 +37,7 @@ const LabelsHeader = ({
|
|||||||
//ToDo: check space permissions as well in case of spaces
|
//ToDo: check space permissions as well in case of spaces
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={css.main} padding={{ top: 'medium', right: 'xlarge', left: 'xlarge', bottom: 'medium' }}>
|
<Container className={css.main} padding={{ top: 'xlarge', right: 'xlarge', left: 'xlarge', bottom: 'medium' }}>
|
||||||
<Layout.Horizontal spacing="medium">
|
<Layout.Horizontal spacing="medium">
|
||||||
<Button
|
<Button
|
||||||
variation={ButtonVariation.PRIMARY}
|
variation={ButtonVariation.PRIMARY}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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: calc(var(--page-height) - 160px);
|
|
||||||
background-color: var(--primary-bg) !important;
|
|
||||||
width: 100%;
|
|
||||||
margin: var(--spacing-small);
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 from 'react'
|
|
||||||
import { PageBody, Page, Layout } from '@harnessio/uicore'
|
|
||||||
import { Render } from 'react-jsx-match'
|
|
||||||
import { useStrings } from 'framework/strings'
|
|
||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
|
||||||
import { useAppContext } from 'AppContext'
|
|
||||||
import LabelsListing from 'pages/Labels/LabelsListing'
|
|
||||||
import { useGetCurrentPageScope } from 'hooks/useGetCurrentPageScope'
|
|
||||||
import css from './ManageLabels.module.scss'
|
|
||||||
|
|
||||||
export default function ManageLabels() {
|
|
||||||
const space = useGetSpaceParam()
|
|
||||||
const { hooks } = useAppContext()
|
|
||||||
const { CODE_PULLREQ_LABELS: isLabelEnabled } = hooks?.useFeatureFlags()
|
|
||||||
const pageScope = useGetCurrentPageScope()
|
|
||||||
const { getString } = useStrings()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Layout.Vertical className={css.main}>
|
|
||||||
<Page.Header title={getString('labels.labels')} />
|
|
||||||
<PageBody>
|
|
||||||
<Render when={!!isLabelEnabled}>
|
|
||||||
<LabelsListing currentPageScope={pageScope} space={space} />
|
|
||||||
</Render>
|
|
||||||
</PageBody>
|
|
||||||
</Layout.Vertical>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.main {
|
||||||
|
min-height: calc(var(--page-height) - 160px);
|
||||||
|
background-color: var(--primary-bg) !important;
|
||||||
|
width: 100%;
|
||||||
|
margin: var(--spacing-small);
|
||||||
|
:global {
|
||||||
|
.bp3-tab {
|
||||||
|
width: fit-content !important;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab-panel {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab-list .bp3-tab[aria-selected='true'] {
|
||||||
|
background-color: var(--grey-0);
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-bottom: 2px solid var(--primary-7);
|
||||||
|
border-bottom-left-radius: 0px !important;
|
||||||
|
border-bottom-right-radius: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabsContainer {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--primary-bg) !important;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div > div[role='tablist'] {
|
||||||
|
background-color: var(--white) !important;
|
||||||
|
padding-left: var(--spacing-large) !important;
|
||||||
|
padding-right: var(--spacing-xlarge) !important;
|
||||||
|
border-bottom: 1px solid var(--grey-200) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div > div[role='tabpanel'] {
|
||||||
|
margin-top: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
[aria-selected='true'] {
|
||||||
|
.tabTitle,
|
||||||
|
.tabTitle:hover {
|
||||||
|
color: var(--grey-900) !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabTitle {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--grey-700);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
margin-top: var(--spacing-8);
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabTitle:not:first-child {
|
||||||
|
margin-left: var(--spacing-8) !important;
|
||||||
|
}
|
||||||
|
}
|
@ -17,3 +17,5 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
export declare const main: string
|
export declare const main: string
|
||||||
|
export declare const tabsContainer: string
|
||||||
|
export declare const tabTitle: string
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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 from 'react'
|
||||||
|
import cx from 'classnames'
|
||||||
|
import { Container, Tabs, Page } from '@harnessio/uicore'
|
||||||
|
import { useHistory, useParams } from 'react-router-dom'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import { useAppContext } from 'AppContext'
|
||||||
|
import BranchProtectionListing from 'components/BranchProtection/BranchProtectionListing'
|
||||||
|
import { SettingsTab } from 'utils/GitUtils'
|
||||||
|
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'
|
||||||
|
|
||||||
|
export default function ManageRepositories() {
|
||||||
|
const { settingSection } = useParams<CODEProps>()
|
||||||
|
const space = useGetSpaceParam()
|
||||||
|
const pageScope = useGetCurrentPageScope()
|
||||||
|
const history = useHistory()
|
||||||
|
const { routes, hooks, standalone } = useAppContext()
|
||||||
|
const { CODE_PULLREQ_LABELS: isLabelEnabled } = hooks?.useFeatureFlags()
|
||||||
|
const [activeTab, setActiveTab] = React.useState<string>(settingSection || SettingsTab.labels)
|
||||||
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
const tabListArray = [
|
||||||
|
...(isLabelEnabled || standalone
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: SettingsTab.labels,
|
||||||
|
title: getString('labels.labels'),
|
||||||
|
panel: <LabelsListing activeTab={activeTab} currentPageScope={pageScope} space={space} />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
id: SettingsTab.branchProtection,
|
||||||
|
title: getString('branchProtection.title'),
|
||||||
|
panel: <BranchProtectionListing activeTab={activeTab} currentPageScope={pageScope} />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<Container className={css.main}>
|
||||||
|
<Page.Header title={getString('manageRepositories')} />
|
||||||
|
|
||||||
|
<Container className={cx(css.main, css.tabsContainer)}>
|
||||||
|
<Tabs
|
||||||
|
id="SettingsTabs"
|
||||||
|
large={false}
|
||||||
|
defaultSelectedTabId={activeTab}
|
||||||
|
animate={false}
|
||||||
|
onChange={(id: string) => {
|
||||||
|
setActiveTab(id)
|
||||||
|
history.replace(
|
||||||
|
routes.toCODEManageRepositories({
|
||||||
|
space,
|
||||||
|
settingSection: id !== SettingsTab.labels ? (id as string) : ''
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
tabList={tabListArray}></Tabs>
|
||||||
|
</Container>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
@ -66,16 +66,6 @@ export default function RepositorySettings() {
|
|||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: SettingsTab.branchProtection,
|
|
||||||
title: getString('branchProtection.title'),
|
|
||||||
panel: <BranchProtectionListing activeTab={activeTab} />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: SettingsTab.security,
|
|
||||||
title: getString('security'),
|
|
||||||
panel: <SecurityScanSettings repoMetadata={repoMetadata} activeTab={activeTab} />
|
|
||||||
},
|
|
||||||
...(isLabelEnabled || standalone
|
...(isLabelEnabled || standalone
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
@ -91,8 +81,23 @@ export default function RepositorySettings() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [])
|
: []),
|
||||||
|
{
|
||||||
|
id: SettingsTab.branchProtection,
|
||||||
|
title: getString('branchProtection.title'),
|
||||||
|
panel: (
|
||||||
|
<BranchProtectionListing
|
||||||
|
repoMetadata={repoMetadata}
|
||||||
|
activeTab={activeTab}
|
||||||
|
currentPageScope={LabelsPageScope.REPOSITORY}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: SettingsTab.security,
|
||||||
|
title: getString('security'),
|
||||||
|
panel: <SecurityScanSettings repoMetadata={repoMetadata} activeTab={activeTab} />
|
||||||
|
}
|
||||||
// {
|
// {
|
||||||
// id: SettingsTab.webhooks,
|
// id: SettingsTab.webhooks,
|
||||||
// title: getString('webhooks'),
|
// title: getString('webhooks'),
|
||||||
|
@ -26,6 +26,7 @@ import { SettingsTab, SpaceSettingsTab } from 'utils/GitUtils'
|
|||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
import LabelsListing from 'pages/Labels/LabelsListing'
|
import LabelsListing from 'pages/Labels/LabelsListing'
|
||||||
import { LabelsPageScope } from 'utils/Utils'
|
import { LabelsPageScope } from 'utils/Utils'
|
||||||
|
import BranchProtectionListing from 'components/BranchProtection/BranchProtectionListing'
|
||||||
import GeneralSpaceSettings from './GeneralSettings/GeneralSpaceSettings'
|
import GeneralSpaceSettings from './GeneralSettings/GeneralSpaceSettings'
|
||||||
import css from './SpaceSettings.module.scss'
|
import css from './SpaceSettings.module.scss'
|
||||||
|
|
||||||
@ -51,6 +52,11 @@ export default function SpaceSettings() {
|
|||||||
id: SettingsTab.labels,
|
id: SettingsTab.labels,
|
||||||
title: getString('labels.labels'),
|
title: getString('labels.labels'),
|
||||||
panel: <LabelsListing activeTab={activeTab} space={space} currentPageScope={LabelsPageScope.SPACE} />
|
panel: <LabelsListing activeTab={activeTab} space={space} currentPageScope={LabelsPageScope.SPACE} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: SettingsTab.branchProtection,
|
||||||
|
title: getString('branchProtection.title'),
|
||||||
|
panel: <BranchProtectionListing activeTab={activeTab} currentPageScope={LabelsPageScope.SPACE} />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
|
@ -610,6 +610,7 @@ export interface OpenapiRule {
|
|||||||
description?: string
|
description?: string
|
||||||
identifier?: string
|
identifier?: string
|
||||||
pattern?: ProtectionPattern
|
pattern?: ProtectionPattern
|
||||||
|
scope?: number
|
||||||
state?: EnumRuleState
|
state?: EnumRuleState
|
||||||
type?: OpenapiRuleType
|
type?: OpenapiRuleType
|
||||||
updated?: number
|
updated?: number
|
||||||
|
@ -40,6 +40,35 @@ export enum ACCESS_MODES {
|
|||||||
EDIT
|
EDIT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ResourceType {
|
||||||
|
ACCOUNT = 'ACCOUNT',
|
||||||
|
ORGANIZATION = 'ORGANIZATION',
|
||||||
|
PROJECT = 'PROJECT',
|
||||||
|
CODE_REPOSITORY = 'CODE_REPOSITORY',
|
||||||
|
SPACE = 'SPACE'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PermissionIdentifier {
|
||||||
|
CREATE_PROJECT = 'core_project_create',
|
||||||
|
UPDATE_PROJECT = 'core_project_edit',
|
||||||
|
DELETE_PROJECT = 'core_project_delete',
|
||||||
|
VIEW_PROJECT = 'core_project_view',
|
||||||
|
CREATE_ORG = 'core_organization_create',
|
||||||
|
UPDATE_ORG = 'core_organization_edit',
|
||||||
|
DELETE_ORG = 'core_organization_delete',
|
||||||
|
VIEW_ORG = 'core_organization_view',
|
||||||
|
CREATE_ACCOUNT = 'core_account_create',
|
||||||
|
UPDATE_ACCOUNT = 'core_account_edit',
|
||||||
|
DELETE_ACCOUNT = 'core_account_delete',
|
||||||
|
VIEW_ACCOUNT = 'core_account_view',
|
||||||
|
CODE_REPO_EDIT = 'code_repo_edit',
|
||||||
|
CODE_REPO_PUSH = 'code_repo_push',
|
||||||
|
CODE_REPO_VIEW = 'code_repo_view',
|
||||||
|
CODE_REPO_REVIEW = 'code_repo_review',
|
||||||
|
SPACE_VIEW = 'space_view',
|
||||||
|
SPACE_EDIT = 'space_edit'
|
||||||
|
}
|
||||||
|
|
||||||
export enum PullRequestSection {
|
export enum PullRequestSection {
|
||||||
CONVERSATION = 'conversation',
|
CONVERSATION = 'conversation',
|
||||||
COMMITS = 'commits',
|
COMMITS = 'commits',
|
||||||
@ -901,6 +930,88 @@ export const getScopeData = (space: string, scope: number, standalone: boolean)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getEditPermissionRequestFromScope = (
|
||||||
|
space: string,
|
||||||
|
scope: number,
|
||||||
|
repoMetadata?: RepoRepositoryOutput
|
||||||
|
) => {
|
||||||
|
const [accountIdentifier, orgIdentifier, projectIdentifier] = space.split('/')
|
||||||
|
|
||||||
|
if (scope === 0 && repoMetadata) {
|
||||||
|
return {
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.CODE_REPOSITORY,
|
||||||
|
resourceIdentifier: repoMetadata?.identifier as string
|
||||||
|
},
|
||||||
|
permissions: [PermissionIdentifier.CODE_REPO_EDIT]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (scope) {
|
||||||
|
case 1:
|
||||||
|
return {
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.ACCOUNT,
|
||||||
|
resourceIdentifier: accountIdentifier as string
|
||||||
|
},
|
||||||
|
permissions: [PermissionIdentifier.UPDATE_ACCOUNT]
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
return {
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.ORGANIZATION,
|
||||||
|
resourceIdentifier: orgIdentifier as string
|
||||||
|
},
|
||||||
|
permissions: [PermissionIdentifier.UPDATE_ORG]
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
return {
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.PROJECT,
|
||||||
|
resourceIdentifier: projectIdentifier as string
|
||||||
|
},
|
||||||
|
permissions: [PermissionIdentifier.UPDATE_PROJECT]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getEditPermissionRequestFromIdentifier = (space: string, repoMetadata?: RepoRepositoryOutput) => {
|
||||||
|
const [accountIdentifier, orgIdentifier, projectIdentifier] = space.split('/')
|
||||||
|
|
||||||
|
if (repoMetadata)
|
||||||
|
return {
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.CODE_REPOSITORY,
|
||||||
|
resourceIdentifier: repoMetadata?.identifier as string
|
||||||
|
},
|
||||||
|
permissions: [PermissionIdentifier.CODE_REPO_EDIT]
|
||||||
|
}
|
||||||
|
else if (projectIdentifier) {
|
||||||
|
return {
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.PROJECT,
|
||||||
|
resourceIdentifier: projectIdentifier as string
|
||||||
|
},
|
||||||
|
permissions: [PermissionIdentifier.UPDATE_PROJECT]
|
||||||
|
}
|
||||||
|
} else if (orgIdentifier) {
|
||||||
|
return {
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.ORGANIZATION,
|
||||||
|
resourceIdentifier: orgIdentifier as string
|
||||||
|
},
|
||||||
|
permissions: [PermissionIdentifier.UPDATE_ORG]
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
return {
|
||||||
|
resource: {
|
||||||
|
resourceType: ResourceType.ACCOUNT,
|
||||||
|
resourceIdentifier: accountIdentifier as string
|
||||||
|
},
|
||||||
|
permissions: [PermissionIdentifier.UPDATE_ACCOUNT]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export enum RuleFields {
|
export enum RuleFields {
|
||||||
APPROVALS_REQUIRE_MINIMUM_COUNT = 'pullreq.approvals.require_minimum_count',
|
APPROVALS_REQUIRE_MINIMUM_COUNT = 'pullreq.approvals.require_minimum_count',
|
||||||
APPROVALS_REQUIRE_CODE_OWNERS = 'pullreq.approvals.require_code_owners',
|
APPROVALS_REQUIRE_CODE_OWNERS = 'pullreq.approvals.require_code_owners',
|
||||||
|
Loading…
Reference in New Issue
Block a user