feat: [CODE-2819]: Archive Repo functionality (#3097)

BT-10437
Atefeh Mohseni Ejiyeh 2025-01-13 23:00:54 +00:00 committed by Harness
parent f78f767ae2
commit e9a7fd1e88
22 changed files with 509 additions and 179 deletions

View File

@ -44,6 +44,7 @@ import { getErrorMessage, timeDistance } from 'utils/Utils'
import useLiveTimer from 'hooks/useLiveTimeHook'
import { CommitActions } from 'components/CommitActions/CommitActions'
import type { TypesExecution } from 'services/code'
import { RepoArchivedBanner } from 'components/RepositoryArchivedBanner/RepositoryArchivedBanner'
import css from './ExecutionPageHeader.module.scss'
interface BreadcrumbLink {
@ -99,119 +100,127 @@ export function ExecutionPageHeader({
}
return (
<PageHeader
className={css.pageHeader}
title={title}
breadcrumbs={
<Layout.Horizontal
spacing="small"
className={css.breadcrumb}
padding={{ bottom: 0 }}
margin={{ bottom: 'small' }}>
<Link to={routes.toCODERepositories({ space })}>{getString('repositories')}</Link>
<Icon name="main-chevron-right" size={8} color={Color.GREY_500} />
<Link to={routes.toCODERepository({ repoPath: repoMetadata.path as string, gitRef })}>
{repoMetadata.identifier}
</Link>
{extraBreadcrumbLinks.map(link => (
<Fragment key={link.url}>
<Icon name="main-chevron-right" size={8} color={Color.GREY_500} />
{/* This allows for outer most entities to not necessarily be links */}
{link.url ? (
<Link to={link.url}>{link.label}</Link>
) : (
<Text font={{ variation: FontVariation.SMALL }} color={Color.GREY_500}>
{link.label}
</Text>
)}
</Fragment>
))}
</Layout.Horizontal>
}
content={
executionInfo && (
<Container className={css.executionInfo}>
<ExecutionStatus status={getStatus(executionInfo.status)} iconOnly noBackground iconSize={18} isCi />
<Text inline lineClamp={1} color={Color.GREY_800} font={{ size: 'small' }}>
{executionInfo.message}
</Text>
<PipeSeparator height={7} />
<Avatar email={executionInfo.authorEmail} name={executionInfo.authorName} size="small" hoverCard={false} />
<Text inline color={Color.GREY_500} font={{ size: 'small' }}>
{executionInfo.authorName}
</Text>
<PipeSeparator height={7} />
<GitFork height={12} width={12} color={Utils.getRealCSSColor(Color.GREY_500)} />
<Text inline color={Color.GREY_500} font={{ size: 'small' }}>
{executionInfo.source}
</Text>
<PipeSeparator height={7} />
{executionInfo.hash && (
<Container onClick={Utils.stopEvent}>
<CommitActions
href={routes.toCODECommit({
repoPath: repoMetadata.path as string,
commitRef: executionInfo.hash
})}
sha={executionInfo.hash}
enableCopy
/>
</Container>
)}
<FlexExpander />
{executionInfo.started && (
<Layout.Horizontal spacing={'small'} style={{ alignItems: 'center' }} className={css.timer}>
<Timer height={16} width={16} color={Utils.getRealCSSColor(Color.GREY_500)} />
<Text inline color={Color.GREY_500} font={{ size: 'small' }}>
{isActive
? timeDistance(executionInfo.started, currentTime, true) // Live update time when status is 'RUNNING'
: timeDistance(executionInfo.started, executionInfo.finished, true)}
</Text>
{executionInfo.finished && (
<>
<PipeSeparator height={7} />
<Calendar height={16} width={16} color={Utils.getRealCSSColor(Color.GREY_500)} />
<Text inline color={Color.GREY_500} font={{ size: 'small' }}>
{timeDistance(executionInfo.finished, currentTime, true)} ago
</Text>
</>
<>
<PageHeader
className={css.pageHeader}
title={title}
breadcrumbs={
<Layout.Horizontal
spacing="small"
className={css.breadcrumb}
padding={{ bottom: 0 }}
margin={{ bottom: 'small' }}>
<Link to={routes.toCODERepositories({ space })}>{getString('repositories')}</Link>
<Icon name="main-chevron-right" size={8} color={Color.GREY_500} />
<Link to={routes.toCODERepository({ repoPath: repoMetadata.path as string, gitRef })}>
{repoMetadata.identifier}
</Link>
{extraBreadcrumbLinks.map(link => (
<Fragment key={link.url}>
<Icon name="main-chevron-right" size={8} color={Color.GREY_500} />
{/* This allows for outer most entities to not necessarily be links */}
{link.url ? (
<Link to={link.url}>{link.label}</Link>
) : (
<Text font={{ variation: FontVariation.SMALL }} color={Color.GREY_500}>
{link.label}
</Text>
)}
</Layout.Horizontal>
)}
<>
</Fragment>
))}
</Layout.Horizontal>
}
content={
executionInfo && (
<Container className={css.executionInfo}>
<ExecutionStatus status={getStatus(executionInfo.status)} iconOnly noBackground iconSize={18} isCi />
<Text inline lineClamp={1} color={Color.GREY_800} font={{ size: 'small' }}>
{executionInfo.message}
</Text>
<PipeSeparator height={7} />
<Button
variation={ButtonVariation.PRIMARY}
text={getString('pipelines.edit')}
onClick={e => {
e.stopPropagation()
if (repoMetadata?.path && pipeline) {
history.push(routes.toCODEPipelineEdit({ repoPath: repoMetadata.path, pipeline }))
}
}}
<Avatar
email={executionInfo.authorEmail}
name={executionInfo.authorName}
size="small"
hoverCard={false}
/>
</>
{[ExecutionState.RUNNING, ExecutionState.PENDING].includes(getStatus(executionInfo?.status)) && (
<Text inline color={Color.GREY_500} font={{ size: 'small' }}>
{executionInfo.authorName}
</Text>
<PipeSeparator height={7} />
<GitFork height={12} width={12} color={Utils.getRealCSSColor(Color.GREY_500)} />
<Text inline color={Color.GREY_500} font={{ size: 'small' }}>
{executionInfo.source}
</Text>
<PipeSeparator height={7} />
{executionInfo.hash && (
<Container onClick={Utils.stopEvent}>
<CommitActions
href={routes.toCODECommit({
repoPath: repoMetadata.path as string,
commitRef: executionInfo.hash
})}
sha={executionInfo.hash}
enableCopy
/>
</Container>
)}
<FlexExpander />
{executionInfo.started && (
<Layout.Horizontal spacing={'small'} style={{ alignItems: 'center' }} className={css.timer}>
<Timer height={16} width={16} color={Utils.getRealCSSColor(Color.GREY_500)} />
<Text inline color={Color.GREY_500} font={{ size: 'small' }}>
{isActive
? timeDistance(executionInfo.started, currentTime, true) // Live update time when status is 'RUNNING'
: timeDistance(executionInfo.started, executionInfo.finished, true)}
</Text>
{executionInfo.finished && (
<>
<PipeSeparator height={7} />
<Calendar height={16} width={16} color={Utils.getRealCSSColor(Color.GREY_500)} />
<Text inline color={Color.GREY_500} font={{ size: 'small' }}>
{timeDistance(executionInfo.finished, currentTime, true)} ago
</Text>
</>
)}
</Layout.Horizontal>
)}
<>
<PipeSeparator height={7} />
<Button
variation={ButtonVariation.SECONDARY}
text={getString('cancel')}
onClick={async () => {
try {
await cancelExecution(null)
clearToaster()
showSuccess(getString('pipelines.executionCancelled'))
} catch (exception) {
showError(getErrorMessage(exception), 0, 'pipelines.executionCouldNotCancel')
variation={ButtonVariation.PRIMARY}
text={getString('pipelines.edit')}
onClick={e => {
e.stopPropagation()
if (repoMetadata?.path && pipeline) {
history.push(routes.toCODEPipelineEdit({ repoPath: repoMetadata.path, pipeline }))
}
}}
/>
</>
)}
</Container>
)
}
/>
{[ExecutionState.RUNNING, ExecutionState.PENDING].includes(getStatus(executionInfo?.status)) && (
<>
<PipeSeparator height={7} />
<Button
variation={ButtonVariation.SECONDARY}
text={getString('cancel')}
onClick={async () => {
try {
await cancelExecution(null)
clearToaster()
showSuccess(getString('pipelines.executionCancelled'))
} catch (exception) {
showError(getErrorMessage(exception), 0, 'pipelines.executionCouldNotCancel')
}
}}
/>
</>
)}
</Container>
)
}
/>
<RepoArchivedBanner isArchived={repoMetadata?.archived} updated={repoMetadata?.updated} />
</>
)
}

View File

@ -147,7 +147,7 @@ const PipelineSettingsTab = ({ pipeline, repoPath, yamlPath }: SettingsContentPr
<Container padding={'large'} className={css.generalContainer}>
<Layout.Vertical>
<Text icon="main-trash" color={Color.GREY_600} font={{ size: 'normal' }}>
{getString('dangerDeleteRepo')}
{getString('dangerDeletePipeline')}
</Text>
<Layout.Horizontal padding={{ top: 'medium', left: 'medium' }} flex={{ justifyContent: 'space-between' }}>
<Container intent="warning" padding={'small'} className={css.yellowContainer}>

View File

@ -26,3 +26,8 @@
padding: 0 9px !important;
display: inline-block !important;
}
.labelArchived {
color: rgb(128, 92, 67, 1) !important;
border-color: rgb(128, 92, 67, 1) !important;
}

View File

@ -17,3 +17,4 @@
/* eslint-disable */
// This is an auto-generated file
export declare const label: string
export declare const labelArchived: string

View File

@ -15,19 +15,32 @@
*/
import React from 'react'
import classNames from 'classnames'
import { Text, TextProps } from '@harnessio/uicore'
import { useStrings } from 'framework/strings'
import css from './RepoPublicLabel.module.scss'
import css from './RepoTypeLabel.module.scss'
export const RepoPublicLabel: React.FC<{ isPublic?: boolean; margin?: TextProps['margin'] }> = ({
export const RepoTypeLabel: React.FC<{ isPublic?: boolean; isArchived?: boolean; margin?: TextProps['margin'] }> = ({
isPublic,
isArchived,
margin
}) => {
const { getString } = useStrings()
const visibility = getString(isPublic ? 'public' : 'private')
const archiveStatus = isArchived ? ` ${getString('archived')}` : ''
return (
<Text inline className={css.label} margin={margin}>
{getString(isPublic ? 'public' : 'private')}
</Text>
<>
<Text inline className={css.label} margin={margin}>
{visibility}
</Text>
{isArchived && (
<Text inline className={classNames(css.label, css.labelArchived)} margin={margin}>
{archiveStatus}
</Text>
)}
</>
)
}

View File

@ -0,0 +1,22 @@
/*
* 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.
*/
.infoContainer {
background-color: var(--orange-100) !important;
padding: var(--spacing-small) var(--spacing-medium) !important;
width: 100% !important;
margin: 0 auto !important;
}

View File

@ -0,0 +1,19 @@
/*
* Copyright 2023 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable */
// This is an auto-generated file
export declare const infoContainer: string

View File

@ -0,0 +1,48 @@
/*
* 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 { Container, Text } from '@harnessio/uicore'
import { Color } from '@harnessio/design-system'
import { useStrings } from 'framework/strings'
import css from './RepositoryArchivedBanner.module.scss'
export const RepoArchivedBanner: React.FC<{ isArchived?: boolean; updated?: number }> = ({ isArchived, updated }) => {
const { getString } = useStrings()
return (
<>
{isArchived && (
<Container className={css.infoContainer}>
<Text
icon="main-issue"
iconProps={{ size: 16, color: Color.ORANGE_700, margin: { right: 'small' } }}
color={Color.GREY_700}>
{getString('repoArchive.infoText', {
date: updated
? new Intl.DateTimeFormat('en-US', {
day: '2-digit',
month: 'short',
year: 'numeric'
}).format(new Date(updated))
: 'N/A'
})}
</Text>
</Container>
)}
</>
)
}

View File

@ -25,6 +25,7 @@ import { useAppContext } from 'AppContext'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import type { CODEProps } from 'RouteDefinitions'
import type { GitInfoProps } from 'utils/GitUtils'
import { RepoArchivedBanner } from 'components/RepositoryArchivedBanner/RepositoryArchivedBanner'
import css from './RepositoryPageHeader.module.scss'
interface BreadcrumbLink {
@ -54,45 +55,48 @@ export function RepositoryPageHeader({
const { routes, isCurrentSessionPublic } = useAppContext()
return (
<PageHeader
className={className}
content={content}
title=""
breadcrumbs={
<Container className={css.header}>
<Layout.Horizontal
spacing="small"
className={cx(css.breadcrumb, { [css.hideBreadcrumbs]: isCurrentSessionPublic })}>
<Link to={routes.toCODERepositories({ space })}>{getString('repositories')}</Link>
<Icon name="main-chevron-right" size={8} color={Color.GREY_500} />
<Link to={routes.toCODERepository({ repoPath: (repoMetadata?.path as string) || '', gitRef })}>
{repoMetadata?.identifier || ''}
</Link>
{extraBreadcrumbLinks.map(link => (
<Fragment key={link.url}>
<Icon name="main-chevron-right" size={8} color={Color.GREY_500} />
{/* This allows for outer most entities to not necessarily be links */}
{link.url ? (
<Link to={link.url}>{link.label}</Link>
) : (
<Text font={{ variation: FontVariation.SMALL }} color={Color.GREY_500}>
{link.label}
</Text>
)}
</Fragment>
))}
</Layout.Horizontal>
<Container padding={{ top: 'small', bottom: 'small' }}>
{typeof title === 'string' ? (
<Text tag="h1" font={{ variation: FontVariation.H4 }} tooltipProps={{ dataTooltipId }}>
{title}
</Text>
) : (
title
)}
<>
<PageHeader
className={className}
content={content}
title=""
breadcrumbs={
<Container className={css.header}>
<Layout.Horizontal
spacing="small"
className={cx(css.breadcrumb, { [css.hideBreadcrumbs]: isCurrentSessionPublic })}>
<Link to={routes.toCODERepositories({ space })}>{getString('repositories')}</Link>
<Icon name="main-chevron-right" size={8} color={Color.GREY_500} />
<Link to={routes.toCODERepository({ repoPath: (repoMetadata?.path as string) || '', gitRef })}>
{repoMetadata?.identifier || ''}
</Link>
{extraBreadcrumbLinks.map(link => (
<Fragment key={link.url}>
<Icon name="main-chevron-right" size={8} color={Color.GREY_500} />
{/* This allows for outer most entities to not necessarily be links */}
{link.url ? (
<Link to={link.url}>{link.label}</Link>
) : (
<Text font={{ variation: FontVariation.SMALL }} color={Color.GREY_500}>
{link.label}
</Text>
)}
</Fragment>
))}
</Layout.Horizontal>
<Container padding={{ top: 'small', bottom: 'small' }}>
{typeof title === 'string' ? (
<Text tag="h1" font={{ variation: FontVariation.H4 }} tooltipProps={{ dataTooltipId }}>
{title}
</Text>
) : (
title
)}
</Container>
</Container>
</Container>
}
/>
}
/>
<RepoArchivedBanner isArchived={repoMetadata?.archived} updated={repoMetadata?.updated} />
</>
)
}

View File

@ -38,6 +38,8 @@ export interface StringsMap {
approve: string
approved: string
approvedBy: string
archive: string
archived: string
artifacts: string
ascending: string
assignPeople: string
@ -151,6 +153,7 @@ export interface StringsMap {
cancelImport: string
cancelImportConfirm: string
cancelledImport: string
cautionZone: string
changePassword: string
changePasswordSuccessfully: string
changeRepoVis: string
@ -300,7 +303,8 @@ export interface StringsMap {
customSecond: string
customTime: string
customizeMergeCommitMessage: string
dangerDeleteRepo: string
dangerDeletePipeline: string
dangerDeleteProject: string
defaultBranch: string
defaultBranchTitle: string
delete: string
@ -312,6 +316,8 @@ export interface StringsMap {
deleteFile: string
deleteImport: string
deleteNotAllowed: string
deleteRepo: string
deleteRepoMsg: string
deleteRepoText: string
deleteRepoTitle: string
deleteSpace: string
@ -917,6 +923,16 @@ export interface StringsMap {
replyAndReactivate: string
replyAndResolve: string
replyHere: string
'repoArchive.archive': string
'repoArchive.archiveInfo': string
'repoArchive.archiveWarning': string
'repoArchive.confirmButton': string
'repoArchive.infoText': string
'repoArchive.titleArchive': string
'repoArchive.titleUnarchive': string
'repoArchive.unarchive': string
'repoArchive.unarchiveInfo': string
'repoArchive.unarchiveWarning': string
repoCloneHeader: string
repoCloneLabel: string
'repoDelete.deleteConfirm1': string
@ -1109,6 +1125,7 @@ export interface StringsMap {
'triggers.updateSuccess': string
turnOnSemanticSearch: string
unableToGetDivergence: string
unarchive: string
unorderedList: string
unrsolvedComment: string
'unsavedChanges.leave': string

View File

@ -163,6 +163,10 @@ createABranch: Create a branch
createATag: Create a tag
delete: Delete
edit: Edit
archive: Archive
unarchive: Unarchive
archived: Archived
cautionZone: Caution Zone
editAsText: Edit as Text
branchName: Branch name
enterBranchPlaceholder: Enter the branch name here
@ -553,9 +557,12 @@ missingPerms: 'You are missing the following permission:'
createRepoPerms: 'Create / Edit Repository'
missingPermsContent: '"{PERMS}" in project "{PROJECT}"'
repositoryName: Repository name
dangerDeleteRepo: Danger, are you sure you want to delete it?
deleteRepo: Delete this repository
deleteRepoMsg: Once a repository is deleted, it cannot be recovered. Proceed with caution.
dangerDeletePipeline: Danger, are you sure you want to delete this pipeline?
dangerDeleteProject: Danger, are you sure you want to delete this project?
repoUpdate: Repository Updated
deleteRepoText: Are you sure you want to delete the repository '{REPONAME}'?
deleteRepoText: Are you sure you want to delete repository '{REPONAME}'?
deleteRepoTitle: Delete the repository
resolve: Resolve
reactivate: Reactivate
@ -738,6 +745,17 @@ repoDelete:
deleteConfirm2: To confirm, type "{{repo}}" in the box below
deleteToastSuccess: Repository deleted successfully
deleteConfirmButton2: Delete this repository
repoArchive:
titleArchive: Archive Repository
titleUnarchive: Unarchive Repository
confirmButton: I understand, {archiveVerb} the repository.
archiveWarning: Archiving this repository will mark it as inactive and set it to read-only. Users can still view and clone the repository, but no new changes or contributions will be allowed.
unarchiveWarning: This repository will no longer be archived and will be restored to an active state. All users and groups with permissions can contribute as usual.
archive: Archive this repository
archiveInfo: Set this repository to archived and restrict it to read-only access.
unarchive: Unarchive this repository
unarchiveInfo: Set this repository to unarchived and make it read-write.
infoText: This repository has been archived on {{date}}. It is now read-only.
pipelines:
noData: There are no pipelines
import: Import Pipelines
@ -964,7 +982,7 @@ enterGithubPlaceholder: https://api.github.com/
enterBitbucketPlaceholder: https://bitbucket.org/
changeRepoVis: Change repository visibility
changeRepoVisContent: Are you sure you want to make this repository {repoVis}?
confirmRepoVisButton: Yes, make the Repository {repoVis}
confirmRepoVisButton: Yes, make the repository {repoVis}
repoVisibility: Repository visibility
visibility: Visibility
attachText: Attach images & videos by dragging & dropping, selecting or pasting them.

View File

@ -49,7 +49,7 @@ import { useAppContext } from 'AppContext'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { NoResultCard } from 'components/NoResultCard/NoResultCard'
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import { RepoPublicLabel } from 'components/RepoPublicLabel/RepoPublicLabel'
import { RepoTypeLabel } from 'components/RepoTypeLabel/RepoTypeLabel'
import KeywordSearch from 'components/CodeSearch/KeywordSearch'
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
import { useConfirmAct } from 'hooks/useConfirmAction'
@ -200,7 +200,11 @@ export default function RepositoriesListing() {
<Layout.Vertical flex className={css.name} ref={rowContainerRef}>
<Text className={css.repoName} width={nameTextWidth} lineClamp={2}>
<Keywords value={searchTerm}>{record.identifier}</Keywords>
<RepoPublicLabel isPublic={row.original.is_public} margin={{ left: 'small' }} />
<RepoTypeLabel
isPublic={row.original.is_public}
isArchived={row.original.archived}
margin={{ left: 'small' }}
/>
</Text>
<Text className={css.desc} width={nameTextWidth} lineClamp={1}>

View File

@ -19,7 +19,7 @@ import { Layout, Text } from '@harnessio/uicore'
import { BookmarkBook } from 'iconoir-react'
import { FontVariation } from '@harnessio/design-system'
import { RepoPublicLabel } from 'components/RepoPublicLabel/RepoPublicLabel'
import { RepoTypeLabel } from 'components/RepoTypeLabel/RepoTypeLabel'
import type { GitInfoProps } from 'utils/GitUtils'
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
import type { RepoRepositoryOutput } from 'services/code'
@ -45,7 +45,7 @@ export function RepositoryHeader(props: RepositoryHeaderProps) {
<Text inline className={css.repoDropdown} font={{ variation: FontVariation.H4 }}>
{repoMetadata.identifier}
</Text>
<RepoPublicLabel isPublic={repoMetadata.is_public} />
<RepoTypeLabel isPublic={repoMetadata.is_public} isArchived={repoMetadata.archived} />
</Layout.Horizontal>
}
dataTooltipId="repositoryTitle"

View File

@ -0,0 +1,124 @@
/*
* 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 {
Container,
Button,
ButtonVariation,
Dialog,
Layout,
Text,
useToaster,
StringSubstitute
} from '@harnessio/uicore'
import { Color, FontVariation } from '@harnessio/design-system'
import { useUpdateRepository } from 'services/code'
import { useModalHook } from 'hooks/useModalHook'
import { useStrings } from 'framework/strings'
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
import { getErrorMessage } from 'utils/Utils'
import css from '../../RepositorySettings.module.scss'
interface ArchiveRepoModalProps {
refetch: () => void
}
const useArchiveRepoModal = ({ refetch: refetchMetaData }: ArchiveRepoModalProps) => {
const { repoMetadata } = useGetRepositoryMetadata()
const { getString } = useStrings()
const { mutate: archiveRepository, loading: archivingRepository } = useUpdateRepository({
repo_ref: `${repoMetadata?.path as string}/+`
})
const { showSuccess, showError } = useToaster()
const [openModal, hideModal] = useModalHook(() => {
const onClose = () => {
hideModal()
}
return (
<Dialog
className={css.dialogContainer}
isOpen
enforceFocus={false}
onClose={onClose}
title={
<Text font={{ variation: FontVariation.H4 }}>
{repoMetadata?.archived === true
? getString('repoArchive.titleUnarchive')
: getString('repoArchive.titleArchive')}
</Text>
}>
<Layout.Vertical spacing="xlarge">
<Container
intent="warning"
background="yellow100"
border={{
color: 'orange500'
}}
margin={{ top: 'medium', bottom: 'medium' }}>
<Text
icon="warning-outline"
iconProps={{ size: 16, margin: { right: 'small' } }}
padding={{ left: 'large', right: 'large', top: 'small', bottom: 'small' }}
color={Color.WARNING}>
{repoMetadata?.archived === true
? getString('repoArchive.unarchiveWarning')
: getString('repoArchive.archiveWarning')}
</Text>
</Container>
<Layout.Horizontal className={css.buttonContainer}>
<Button
type="submit"
variation={ButtonVariation.PRIMARY}
margin={{ right: 'small' }}
onClick={async () => {
try {
await archiveRepository({ state: repoMetadata?.archived ? 0 : 4 })
showSuccess(getString('repoUpdate'))
hideModal()
refetchMetaData()
} catch (exception) {
showError(getErrorMessage(exception), 0, 'failed to archive the repository')
}
refetchMetaData()
}}>
<StringSubstitute
str={getString('repoArchive.confirmButton')}
vars={{
archiveVerb: <span className={css.text}>{repoMetadata?.archived ? 'unarchive' : 'archive'}</span>
}}
/>
</Button>
<Button
text={getString('cancel')}
variation={ButtonVariation.TERTIARY}
onClick={() => {
hideModal()
}}
/>
</Layout.Horizontal>
</Layout.Vertical>
</Dialog>
)
}, [archivingRepository, repoMetadata])
return {
openModal,
hideModal
}
}
export default useArchiveRepoModal

View File

@ -111,7 +111,6 @@ const useDeleteRepoModal = () => {
margin={{ top: 'small' }}
onClick={async () => {
try {
// this isn't implemented in the backend yet
await deleteRepo(`${repoMetadata?.path as string}/+/`)
setShowConfirmPage(false)
setDeleteConfirmString('')

View File

@ -39,12 +39,13 @@ import { useStrings } from 'framework/strings'
import type { RepoRepositoryOutput } from 'services/code'
import { useAppContext } from 'AppContext'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import { RepoVisibility } from 'utils/GitUtils'
import { RepoVisibility, RepoState } from 'utils/GitUtils'
import { BranchTagSelect } from 'components/BranchTagSelect/BranchTagSelect'
import { useModalHook } from 'hooks/useModalHook'
import { usePublicResourceConfig } from 'hooks/usePublicResourceConfig'
import useDeleteRepoModal from './DeleteRepoModal/DeleteRepoModal'
import useDefaultBranchModal from './DefaultBranchModal/DefaultBranchModal'
import useArchiveRepoModal from './ArchiveRepoModal/ArchiveRepoModal'
import Private from '../../../icons/private.svg?url'
import css from '../RepositorySettings.module.scss'
@ -68,6 +69,8 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
const { allowPublicResourceCreation } = usePublicResourceConfig()
const { getString } = useStrings()
const currRepoVisibility = repoMetadata?.is_public === true ? RepoVisibility.PUBLIC : RepoVisibility.PRIVATE
const repoState = repoMetadata?.archived === true ? RepoState.ARCHIVED : RepoState.UNARCHIVED
const { openModal: openArchiveRepoModal } = useArchiveRepoModal({ refetch })
const [repoVis, setRepoVis] = useState<RepoVisibility>(currRepoVisibility)
const { mutate } = useMutate({
@ -186,7 +189,7 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
{formik => {
return (
<Layout.Vertical padding={{ top: 'medium' }}>
<Container padding="large" margin={{ bottom: 'medium' }} className={css.generalContainer}>
<Container padding="medium" margin={{ bottom: 'medium' }} className={css.generalContainer}>
<Layout.Horizontal padding={{ bottom: 'medium' }}>
<Container className={css.label}>
<Text color={Color.GREY_600} className={css.textSize}>
@ -409,20 +412,51 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
</Layout.Horizontal>
</Container>
</Render>
<Container padding="medium" className={css.generalContainer}>
<Container className={css.deleteContainer}>
<Text icon="main-trash" color={Color.GREY_600} font={{ size: 'small' }}>
{getString('dangerDeleteRepo')}
</Text>
<Button
intent={Intent.DANGER}
onClick={() => {
openDeleteRepoModal()
}}
variation={ButtonVariation.SECONDARY}
text={getString('delete')}
{...permissionProps(permDeleteResult, standalone)}></Button>
</Container>
<Container padding="medium" margin={{ bottom: 'medium' }} className={css.generalContainer}>
<Layout.Horizontal padding={{ bottom: 'medium' }}>
<Container className={css.label}>
<Text color={Color.GREY_600} className={css.textSize} margin={{ top: 'medium' }}>
{getString('cautionZone')}
</Text>
</Container>
<Layout.Vertical>
<Container className={css.cautionContainer}>
<Layout.Vertical spacing="small" padding={{ right: 'large' }}>
<Text font={{ size: 'small' }} className={css.textSize}>
{repoState === RepoState.ARCHIVED
? getString('repoArchive.unarchive')
: getString('repoArchive.archive')}
</Text>
<Text font={{ variation: FontVariation.TINY }}>
{repoState === RepoState.ARCHIVED
? getString('repoArchive.unarchiveInfo')
: getString('repoArchive.archiveInfo')}
</Text>
</Layout.Vertical>
<Button
onClick={openArchiveRepoModal}
variation={ButtonVariation.SECONDARY}
text={repoState === RepoState.ARCHIVED ? getString('unarchive') : getString('archive')}
{...permissionProps(permEditResult, standalone)}
/>
</Container>
<Container className={css.cautionContainer}>
<Layout.Vertical spacing="small" padding={{ right: 'large' }}>
<Text font={{ size: 'small' }} className={css.textSize}>
{getString('deleteRepo')}
</Text>
<Text font={{ variation: FontVariation.TINY }}>{getString('deleteRepoMsg')}</Text>
</Layout.Vertical>
<Button
intent={Intent.DANGER}
onClick={openDeleteRepoModal}
variation={ButtonVariation.SECONDARY}
text={getString('delete')}
{...permissionProps(permDeleteResult, standalone)}
/>
</Container>
</Layout.Vertical>
</Layout.Horizontal>
</Container>
</Layout.Vertical>
)

View File

@ -104,9 +104,13 @@
width: 80%;
}
.deleteContainer {
display: flex;
justify-content: space-between;
.cautionContainer {
display: flex !important;
justify-content: space-between !important;
align-items: center;
width: 100% !important;
padding: var(--spacing-medium) var(--spacing-small) !important;
white-space: nowrap !important;
}
.text {

View File

@ -18,9 +18,9 @@
// This is an auto-generated file
export declare const btn: string
export declare const buttonContainer: string
export declare const cautionContainer: string
export declare const content: string
export declare const contentContainer: string
export declare const deleteContainer: string
export declare const description: string
export declare const descText: string
export declare const dialogContainer: string

View File

@ -479,7 +479,7 @@ export default function GeneralSpaceSettings() {
<Container className={css.deleteContainer}>
<Layout.Vertical className={css.verticalContainer}>
<Text icon="main-trash" color={Color.GREY_600} font={{ size: 'small' }}>
{getString('dangerDeleteRepo')}
{getString('dangerDeleteProject')}
</Text>
<Layout.Horizontal
padding={{ top: 'medium', left: 'medium' }}

View File

@ -667,6 +667,7 @@ export interface OpenapiUpdateRepoPublicAccessRequest {
export interface OpenapiUpdateRepoRequest {
description?: string | null
state?: number | null
}
export interface OpenapiUpdateRepoWebhookRequest {
@ -878,6 +879,7 @@ export interface RepoRepositoryOutput {
importing?: boolean
is_empty?: boolean
is_public?: boolean
archived?: boolean
num_closed_pulls?: number
num_forks?: number
num_merged_pulls?: number

View File

@ -12758,6 +12758,8 @@ components:
type: object
RepoRepositoryOutput:
properties:
archived:
type: boolean
created:
type: integer
created_by:

View File

@ -103,6 +103,11 @@ export enum RepoVisibility {
PRIVATE = 'private'
}
export enum RepoState {
ARCHIVED = 'archived',
UNARCHIVED = 'unarchived'
}
export enum RepoCreationType {
IMPORT = 'import',
CREATE = 'create',