mirror of
https://github.com/harness/drone.git
synced 2025-05-31 11:43:15 +00:00
Merge branch 'main' into add-V1-schema
This commit is contained in:
commit
cd07a34559
191
web/src/components/NewRepoModalButton/ImportForm/ImportForm.tsx
Normal file
191
web/src/components/NewRepoModalButton/ImportForm/ImportForm.tsx
Normal file
@ -0,0 +1,191 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Intent } from '@blueprintjs/core'
|
||||
import * as yup from 'yup'
|
||||
import { Button, Container, Layout, FlexExpander, Formik, FormikForm, FormInput, Text } from '@harnessio/uicore'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { FontVariation } from '@harnessio/design-system'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { REGEX_VALID_REPO_NAME } from 'utils/Utils'
|
||||
import { ImportFormData, RepoVisibility, parseUrl } from 'utils/GitUtils'
|
||||
import css from '../NewRepoModalButton.module.scss'
|
||||
|
||||
interface ImportFormProps {
|
||||
handleSubmit: (data: ImportFormData) => void
|
||||
loading: boolean // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
hideModal: any
|
||||
}
|
||||
|
||||
const ImportForm = (props: ImportFormProps) => {
|
||||
const { handleSubmit, loading, hideModal } = props
|
||||
const { getString } = useStrings()
|
||||
const [auth, setAuth] = useState(false)
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const MATCH_REPOURL_REGEX = /^(https?:\/\/(?:www\.)?(github|gitlab)\.com\/([^/]+\/[^/]+))/
|
||||
|
||||
const formInitialValues: ImportFormData = {
|
||||
repoUrl: '',
|
||||
username: '',
|
||||
password: '',
|
||||
name: '',
|
||||
description: '',
|
||||
isPublic: RepoVisibility.PRIVATE
|
||||
}
|
||||
return (
|
||||
<Formik
|
||||
initialValues={formInitialValues}
|
||||
formName="editVariations"
|
||||
validationSchema={yup.object().shape({
|
||||
repoUrl: yup
|
||||
.string()
|
||||
.matches(MATCH_REPOURL_REGEX, getString('importSpace.invalidUrl'))
|
||||
.required(getString('importRepo.required')),
|
||||
name: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required(getString('validation.nameIsRequired'))
|
||||
.matches(REGEX_VALID_REPO_NAME, getString('validation.repoNamePatternIsNotValid')),
|
||||
username: yup.string().trim().required(getString('importRepo.usernameReq')),
|
||||
password: yup.string().trim().required(getString('importRepo.passwordReq'))
|
||||
})}
|
||||
onSubmit={handleSubmit}>
|
||||
{formik => {
|
||||
return (
|
||||
<FormikForm>
|
||||
<FormInput.Text
|
||||
name="repoUrl"
|
||||
label={getString('importRepo.url')}
|
||||
placeholder={getString('importRepo.urlPlaceholder')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'repositoryURLTextField'
|
||||
}}
|
||||
onChange={event => {
|
||||
const target = event.target as HTMLInputElement
|
||||
formik.setFieldValue('repoUrl', target.value)
|
||||
if (target.value) {
|
||||
const provider = parseUrl(target.value)
|
||||
if (provider?.fullRepo) {
|
||||
formik.setFieldValue('name', provider.repoName ? provider.repoName : provider?.fullRepo)
|
||||
formik.validateField('repoUrl')
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FormInput.CheckBox
|
||||
name="authorization"
|
||||
label={getString('importRepo.reqAuth')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'authorization'
|
||||
}}
|
||||
onClick={() => {
|
||||
setAuth(!auth)
|
||||
}}
|
||||
/>
|
||||
|
||||
{auth ? (
|
||||
<>
|
||||
<FormInput.Text
|
||||
name="username"
|
||||
label={getString('userName')}
|
||||
placeholder={getString('importRepo.userPlaceholder')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'repositoryUserTextField'
|
||||
}}
|
||||
/>
|
||||
<FormInput.Text
|
||||
inputGroup={{ type: 'password' }}
|
||||
name="password"
|
||||
label={getString('importRepo.passToken')}
|
||||
placeholder={getString('importRepo.passwordPlaceholder')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'repositoryPasswordTextField'
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
<hr className={css.dividerContainer} />
|
||||
<FormInput.Text
|
||||
name="name"
|
||||
label={getString('name')}
|
||||
placeholder={getString('enterRepoName')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'repositoryNameTextField'
|
||||
}}
|
||||
onChange={() => {
|
||||
formik.validateField('repoUrl')
|
||||
}}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name="description"
|
||||
label={getString('description')}
|
||||
placeholder={getString('enterDescription')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'repositoryDescriptionTextField'
|
||||
}}
|
||||
/>
|
||||
|
||||
<hr className={css.dividerContainer} />
|
||||
|
||||
<Container>
|
||||
<FormInput.RadioGroup
|
||||
name="isPublic"
|
||||
label=""
|
||||
items={[
|
||||
{
|
||||
label: (
|
||||
<Container>
|
||||
<Layout.Horizontal>
|
||||
<Icon name="git-clone-step" size={20} margin={{ right: 'medium' }} />
|
||||
<Container>
|
||||
<Layout.Vertical spacing="xsmall">
|
||||
<Text>{getString('public')}</Text>
|
||||
<Text font={{ variation: FontVariation.TINY }}>
|
||||
{getString('createRepoModal.publicLabel')}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
),
|
||||
value: RepoVisibility.PUBLIC
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Container>
|
||||
<Layout.Horizontal>
|
||||
<Icon name="git-clone-step" size={20} margin={{ right: 'medium' }} />
|
||||
<Container margin={{ left: 'small' }}>
|
||||
<Layout.Vertical spacing="xsmall">
|
||||
<Text>{getString('private')}</Text>
|
||||
<Text font={{ variation: FontVariation.TINY }}>
|
||||
{getString('createRepoModal.privateLabel')}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
),
|
||||
value: RepoVisibility.PRIVATE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
<Layout.Horizontal
|
||||
spacing="small"
|
||||
padding={{ right: 'xxlarge', top: 'xlarge', bottom: 'large' }}
|
||||
style={{ alignItems: 'center' }}>
|
||||
<Button type="submit" text={getString('importRepo.title')} intent={Intent.PRIMARY} disabled={loading} />
|
||||
<Button text={getString('cancel')} minimal onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
)
|
||||
}}
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImportForm
|
@ -1,3 +1,29 @@
|
||||
.divider {
|
||||
margin: var(--spacing-medium) 0 var(--spacing-large) 0 !important;
|
||||
}
|
||||
|
||||
.dividerContainer {
|
||||
opacity: 0.2;
|
||||
height: 1px;
|
||||
color: var(--grey-100);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.popover {
|
||||
transform: translateY(5px) !important;
|
||||
|
||||
.menuItem {
|
||||
strong {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
padding: 0 var(--spacing-xlarge);
|
||||
line-height: 16px;
|
||||
margin: 5px 0;
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const divider: string
|
||||
export declare const dividerContainer: string
|
||||
export declare const menuItem: string
|
||||
export declare const popover: string
|
||||
|
@ -1,5 +1,14 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Icon as BPIcon, Classes, Dialog, Intent, Menu, MenuDivider, MenuItem } from '@blueprintjs/core'
|
||||
import {
|
||||
Icon as BPIcon,
|
||||
Classes,
|
||||
Dialog,
|
||||
Intent,
|
||||
Menu,
|
||||
MenuDivider,
|
||||
MenuItem,
|
||||
PopoverPosition
|
||||
} from '@blueprintjs/core'
|
||||
import * as yup from 'yup'
|
||||
import {
|
||||
Button,
|
||||
@ -15,7 +24,8 @@ import {
|
||||
Text,
|
||||
ButtonVariation,
|
||||
ButtonSize,
|
||||
TextInput
|
||||
TextInput,
|
||||
SplitButton
|
||||
} from '@harnessio/uicore'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { FontVariation } from '@harnessio/design-system'
|
||||
@ -30,26 +40,19 @@ import {
|
||||
REGEX_VALID_REPO_NAME,
|
||||
SUGGESTED_BRANCH_NAMES
|
||||
} from 'utils/Utils'
|
||||
import { isGitBranchNameValid } from 'utils/GitUtils'
|
||||
import {
|
||||
ImportFormData,
|
||||
RepoCreationType,
|
||||
RepoFormData,
|
||||
RepoVisibility,
|
||||
isGitBranchNameValid,
|
||||
parseUrl
|
||||
} from 'utils/GitUtils'
|
||||
import type { TypesRepository, OpenapiCreateRepositoryRequest } from 'services/code'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import ImportForm from './ImportForm/ImportForm'
|
||||
import css from './NewRepoModalButton.module.scss'
|
||||
|
||||
enum RepoVisibility {
|
||||
PUBLIC = 'public',
|
||||
PRIVATE = 'private'
|
||||
}
|
||||
|
||||
interface RepoFormData {
|
||||
name: string
|
||||
description: string
|
||||
license: string
|
||||
defaultBranch: string
|
||||
gitignore: string
|
||||
addReadme: boolean
|
||||
isPublic: RepoVisibility
|
||||
}
|
||||
|
||||
const formInitialValues: RepoFormData = {
|
||||
name: '',
|
||||
description: '',
|
||||
@ -90,6 +93,15 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
||||
space_path: space
|
||||
}
|
||||
})
|
||||
const { mutate: importRepo, loading: importRepoLoading } = useMutate<TypesRepository>({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/repos/import`,
|
||||
queryParams: standalone
|
||||
? undefined
|
||||
: {
|
||||
space_path: space
|
||||
}
|
||||
})
|
||||
const {
|
||||
data: gitignores,
|
||||
loading: gitIgnoreLoading,
|
||||
@ -100,14 +112,13 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
||||
loading: licenseLoading,
|
||||
error: licenseError
|
||||
} = useGet({ path: '/api/v1/resources/license' })
|
||||
const loading = submitLoading || gitIgnoreLoading || licenseLoading
|
||||
const loading = submitLoading || gitIgnoreLoading || licenseLoading || importRepoLoading
|
||||
|
||||
useEffect(() => {
|
||||
if (gitIgnoreError || licenseError) {
|
||||
showError(getErrorMessage(gitIgnoreError || licenseError), 0)
|
||||
}
|
||||
}, [gitIgnoreError, licenseError, showError])
|
||||
|
||||
const handleSubmit = (formData: RepoFormData) => {
|
||||
try {
|
||||
const payload: OpenapiCreateRepositoryRequest = {
|
||||
@ -132,7 +143,24 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
||||
showError(getErrorMessage(exception), 0, getString('failedToCreateRepo'))
|
||||
}
|
||||
}
|
||||
|
||||
const handleImportSubmit = (formData: ImportFormData) => {
|
||||
const provider = parseUrl(formData.repoUrl)
|
||||
const importPayload = {
|
||||
description: formData.description || '',
|
||||
parent_ref: space,
|
||||
uid: formData.name,
|
||||
provider: { type: provider?.provider.toLowerCase(), username: formData.username, password: formData.password },
|
||||
provider_repo: provider?.fullRepo
|
||||
}
|
||||
importRepo(importPayload)
|
||||
.then(response => {
|
||||
hideModal()
|
||||
onSubmit(response)
|
||||
})
|
||||
.catch(_error => {
|
||||
showError(getErrorMessage(_error), 0, getString('importRepo.failedToImportRepo'))
|
||||
})
|
||||
}
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
@ -145,147 +173,166 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
||||
style={{ height: '100%' }}
|
||||
data-testid="add-target-to-flag-modal">
|
||||
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
|
||||
{modalTitle}
|
||||
{repoOption.type === RepoCreationType.IMPORT ? getString('importRepo.title') : modalTitle}
|
||||
</Heading>
|
||||
|
||||
<Container margin={{ right: 'xxlarge' }}>
|
||||
<Formik
|
||||
initialValues={formInitialValues}
|
||||
formName="editVariations"
|
||||
enableReinitialize={true}
|
||||
validationSchema={yup.object().shape({
|
||||
name: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required()
|
||||
.matches(REGEX_VALID_REPO_NAME, getString('validation.repoNamePatternIsNotValid'))
|
||||
})}
|
||||
validateOnChange
|
||||
validateOnBlur
|
||||
onSubmit={handleSubmit}>
|
||||
<FormikForm>
|
||||
<FormInput.Text
|
||||
name="name"
|
||||
label={getString('name')}
|
||||
placeholder={getString('enterRepoName')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'repositoryNameTextField'
|
||||
}}
|
||||
inputGroup={{ autoFocus: true }}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name="description"
|
||||
label={getString('description')}
|
||||
placeholder={getString('enterDescription')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'repositoryDescriptionTextField'
|
||||
}}
|
||||
/>
|
||||
<Container margin={{ top: 'medium', bottom: 'medium' }}>
|
||||
<Text>
|
||||
{getString('createRepoModal.branchLabel')}
|
||||
<strong>
|
||||
<Button
|
||||
text={branchName}
|
||||
icon="git-new-branch"
|
||||
rightIcon="chevron-down"
|
||||
variation={ButtonVariation.TERTIARY}
|
||||
size={ButtonSize.SMALL}
|
||||
iconProps={{ size: 14 }}
|
||||
tooltip={<BranchName currentBranchName={branchName} onSelect={name => setBranchName(name)} />}
|
||||
tooltipProps={{ interactionKind: 'click' }}
|
||||
/>
|
||||
</strong>
|
||||
{getString('createRepoModal.branch')}
|
||||
</Text>
|
||||
</Container>
|
||||
<Container>
|
||||
<FormInput.RadioGroup
|
||||
name="isPublic"
|
||||
label=""
|
||||
items={[
|
||||
{
|
||||
label: (
|
||||
<Container>
|
||||
<Layout.Horizontal>
|
||||
<Icon name="git-clone-step" size={20} margin={{ right: 'medium' }} />
|
||||
<Container>
|
||||
<Layout.Vertical spacing="xsmall">
|
||||
<Text>{getString('public')}</Text>
|
||||
<Text font={{ variation: FontVariation.TINY }}>
|
||||
{getString('createRepoModal.publicLabel')}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
),
|
||||
value: RepoVisibility.PUBLIC
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Container>
|
||||
<Layout.Horizontal>
|
||||
<Icon name="git-clone-step" size={20} margin={{ right: 'medium' }} />
|
||||
<Container margin={{ left: 'small' }}>
|
||||
<Layout.Vertical spacing="xsmall">
|
||||
<Text>{getString('private')}</Text>
|
||||
<Text font={{ variation: FontVariation.TINY }}>
|
||||
{getString('createRepoModal.privateLabel')}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
),
|
||||
value: RepoVisibility.PRIVATE
|
||||
}
|
||||
]}
|
||||
{repoOption.type === RepoCreationType.IMPORT ? (
|
||||
<ImportForm hideModal={hideModal} handleSubmit={handleImportSubmit} loading={false} />
|
||||
) : (
|
||||
<Formik
|
||||
initialValues={formInitialValues}
|
||||
formName="editVariations"
|
||||
enableReinitialize={true}
|
||||
validationSchema={yup.object().shape({
|
||||
name: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required()
|
||||
.matches(REGEX_VALID_REPO_NAME, getString('validation.repoNamePatternIsNotValid'))
|
||||
})}
|
||||
validateOnChange
|
||||
validateOnBlur
|
||||
onSubmit={handleSubmit}>
|
||||
<FormikForm>
|
||||
<FormInput.Text
|
||||
name="name"
|
||||
label={getString('name')}
|
||||
placeholder={getString('enterRepoName')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'repositoryNameTextField'
|
||||
}}
|
||||
inputGroup={{ autoFocus: true }}
|
||||
/>
|
||||
</Container>
|
||||
<FormInput.Text
|
||||
name="description"
|
||||
label={getString('description')}
|
||||
placeholder={getString('enterDescription')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'repositoryDescriptionTextField'
|
||||
}}
|
||||
/>
|
||||
<Container margin={{ top: 'medium', bottom: 'medium' }}>
|
||||
<Text>
|
||||
{getString('createRepoModal.branchLabel')}
|
||||
<strong>
|
||||
<Button
|
||||
text={branchName}
|
||||
icon="git-new-branch"
|
||||
rightIcon="chevron-down"
|
||||
variation={ButtonVariation.TERTIARY}
|
||||
size={ButtonSize.SMALL}
|
||||
iconProps={{ size: 14 }}
|
||||
tooltip={<BranchName currentBranchName={branchName} onSelect={name => setBranchName(name)} />}
|
||||
tooltipProps={{ interactionKind: 'click' }}
|
||||
/>
|
||||
</strong>
|
||||
{getString('createRepoModal.branch')}
|
||||
</Text>
|
||||
</Container>
|
||||
<Container>
|
||||
<FormInput.RadioGroup
|
||||
name="isPublic"
|
||||
label=""
|
||||
items={[
|
||||
{
|
||||
label: (
|
||||
<Container>
|
||||
<Layout.Horizontal>
|
||||
<Icon name="git-clone-step" size={20} margin={{ right: 'medium' }} />
|
||||
<Container>
|
||||
<Layout.Vertical spacing="xsmall">
|
||||
<Text>{getString('public')}</Text>
|
||||
<Text font={{ variation: FontVariation.TINY }}>
|
||||
{getString('createRepoModal.publicLabel')}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
),
|
||||
value: RepoVisibility.PUBLIC
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Container>
|
||||
<Layout.Horizontal>
|
||||
<Icon name="git-clone-step" size={20} margin={{ right: 'medium' }} />
|
||||
<Container margin={{ left: 'small' }}>
|
||||
<Layout.Vertical spacing="xsmall">
|
||||
<Text>{getString('private')}</Text>
|
||||
<Text font={{ variation: FontVariation.TINY }}>
|
||||
{getString('createRepoModal.privateLabel')}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
),
|
||||
value: RepoVisibility.PRIVATE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
<FormInput.Select
|
||||
name="license"
|
||||
label={getString('addLicense')}
|
||||
placeholder={getString('none')}
|
||||
items={licences || []}
|
||||
usePortal
|
||||
/>
|
||||
<FormInput.Select
|
||||
name="license"
|
||||
label={getString('addLicense')}
|
||||
placeholder={getString('none')}
|
||||
items={licences || []}
|
||||
usePortal
|
||||
/>
|
||||
|
||||
<FormInput.Select
|
||||
name="gitignore"
|
||||
label={getString('addGitIgnore')}
|
||||
placeholder={getString('none')}
|
||||
items={(gitignores || []).map((entry: string) => ({ label: entry, value: entry }))}
|
||||
usePortal
|
||||
/>
|
||||
<FormInput.Select
|
||||
name="gitignore"
|
||||
label={getString('addGitIgnore')}
|
||||
placeholder={getString('none')}
|
||||
items={(gitignores || []).map((entry: string) => ({ label: entry, value: entry }))}
|
||||
usePortal
|
||||
/>
|
||||
|
||||
<FormInput.CheckBox
|
||||
name="addReadme"
|
||||
label={getString('addReadMe')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'addReadMe'
|
||||
}}
|
||||
/>
|
||||
<Layout.Horizontal
|
||||
spacing="small"
|
||||
padding={{ right: 'xxlarge', top: 'xxxlarge', bottom: 'large' }}
|
||||
style={{ alignItems: 'center' }}>
|
||||
<Button type="submit" text={getString('createRepo')} intent={Intent.PRIMARY} disabled={loading} />
|
||||
<Button text={cancelButtonTitle || getString('cancel')} minimal onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
<FormInput.CheckBox
|
||||
name="addReadme"
|
||||
label={getString('addReadMe')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'addReadMe'
|
||||
}}
|
||||
/>
|
||||
<Layout.Horizontal
|
||||
spacing="small"
|
||||
padding={{ right: 'xxlarge', top: 'xxxlarge', bottom: 'large' }}
|
||||
style={{ alignItems: 'center' }}>
|
||||
<Button type="submit" text={getString('createRepo')} intent={Intent.PRIMARY} disabled={loading} />
|
||||
<Button text={cancelButtonTitle || getString('cancel')} minimal onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
)}
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
const { getString } = useStrings()
|
||||
|
||||
const [openModal, hideModal] = useModalHook(ModalComponent, [onSubmit])
|
||||
const repoCreateOptions: RepoCreationOption[] = [
|
||||
{
|
||||
type: RepoCreationType.CREATE,
|
||||
title: getString('newRepo'),
|
||||
desc: getString('createNewRepo')
|
||||
},
|
||||
{
|
||||
type: RepoCreationType.IMPORT,
|
||||
title: getString('importGitRepo'),
|
||||
desc: getString('importGitRepo')
|
||||
}
|
||||
]
|
||||
const [repoOption, setRepoOption] = useState<RepoCreationOption>(repoCreateOptions[0])
|
||||
|
||||
const [openModal, hideModal] = useModalHook(ModalComponent, [onSubmit, repoOption])
|
||||
const { standalone } = useAppContext()
|
||||
const { hooks } = useAppContext()
|
||||
const permResult = hooks?.usePermissionTranslate?.(
|
||||
@ -297,8 +344,48 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
||||
},
|
||||
[space]
|
||||
)
|
||||
return (
|
||||
<SplitButton
|
||||
{...props}
|
||||
loading={false}
|
||||
text={repoOption.title}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
popoverProps={{
|
||||
interactionKind: 'click',
|
||||
usePortal: true,
|
||||
popoverClassName: css.popover,
|
||||
position: PopoverPosition.BOTTOM_RIGHT,
|
||||
transitionDuration: 1000
|
||||
}}
|
||||
icon={repoOption.type === RepoCreationType.IMPORT ? undefined : 'plus'}
|
||||
{...permissionProps(permResult, standalone)}
|
||||
onClick={() => {
|
||||
openModal()
|
||||
}}>
|
||||
{repoCreateOptions.map(option => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={option.type}
|
||||
className={css.menuItem}
|
||||
text={
|
||||
<>
|
||||
<p>{option.desc}</p>
|
||||
</>
|
||||
}
|
||||
onClick={() => {
|
||||
setRepoOption(option)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</SplitButton>
|
||||
)
|
||||
}
|
||||
|
||||
return <Button onClick={openModal} {...props} {...permissionProps(permResult, standalone)} />
|
||||
interface RepoCreationOption {
|
||||
type: RepoCreationType
|
||||
title: string
|
||||
desc: string
|
||||
}
|
||||
|
||||
interface BranchNameProps {
|
||||
|
@ -0,0 +1,320 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Intent } from '@blueprintjs/core'
|
||||
import * as yup from 'yup'
|
||||
import { Color } from '@harnessio/design-system'
|
||||
import { Button, Container, Label, Layout, FlexExpander, Formik, FormikForm, FormInput, Text } from '@harnessio/uicore'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { Organization, type ImportSpaceFormData } from 'utils/GitUtils'
|
||||
import css from '../NewSpaceModalButton.module.scss'
|
||||
|
||||
interface ImportFormProps {
|
||||
handleSubmit: (data: ImportSpaceFormData) => void
|
||||
loading: boolean
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
hideModal: any
|
||||
}
|
||||
|
||||
const ImportSpaceForm = (props: ImportFormProps) => {
|
||||
const { handleSubmit, loading, hideModal } = props
|
||||
const { getString } = useStrings()
|
||||
const [auth, setAuth] = useState(false)
|
||||
const [step, setStep] = useState(0)
|
||||
const [buttonLoading, setButtonLoading] = useState(false)
|
||||
|
||||
const formInitialValues: ImportSpaceFormData = {
|
||||
gitProvider: '',
|
||||
username: '',
|
||||
password: '',
|
||||
name: '',
|
||||
description: '',
|
||||
organization: ''
|
||||
}
|
||||
const providers = [
|
||||
{ value: 'Github', label: 'Github' },
|
||||
{ value: 'Gitlab', label: 'Gitlab' }
|
||||
]
|
||||
const validationSchemaStepOne = yup.object().shape({
|
||||
gitProvider: yup.string().trim().required(getString('importSpace.providerRequired')),
|
||||
username: yup.string().trim().required(getString('importRepo.usernameReq')),
|
||||
password: yup.string().trim().required(getString('importRepo.passwordReq'))
|
||||
})
|
||||
|
||||
const validationSchemaStepTwo = yup.object().shape({
|
||||
organization: yup.string().trim().required(getString('importSpace.orgRequired')),
|
||||
name: yup.string().trim().required(getString('importSpace.spaceNameRequired'))
|
||||
})
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={formInitialValues}
|
||||
formName="editVariations"
|
||||
enableReinitialize={true}
|
||||
validateOnBlur
|
||||
onSubmit={handleSubmit}>
|
||||
{formik => {
|
||||
const { values } = formik
|
||||
const handleValidationClick = async () => {
|
||||
try {
|
||||
if (step === 0) {
|
||||
await validationSchemaStepOne.validate(formik.values, { abortEarly: false })
|
||||
setStep(1)
|
||||
} else if (step === 1) {
|
||||
await validationSchemaStepTwo.validate(formik.values, { abortEarly: false })
|
||||
setButtonLoading(true)
|
||||
} // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
formik.setErrors(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
err.inner.reduce((acc: { [x: string]: any }, current: { path: string | number; message: string }) => {
|
||||
acc[current.path] = current.message
|
||||
return acc
|
||||
}, {})
|
||||
)
|
||||
}
|
||||
}
|
||||
const handleImport = async () => {
|
||||
console.log('ds')
|
||||
await handleSubmit(formik.values)
|
||||
console.log('pos')
|
||||
setButtonLoading(false)
|
||||
}
|
||||
return (
|
||||
<Container width={'97%'}>
|
||||
<FormikForm>
|
||||
{step === 0 ? (
|
||||
<>
|
||||
<Container width={'70%'}>
|
||||
<Layout.Horizontal>
|
||||
<Icon className={css.icon} name="code-info" size={16} />
|
||||
<Text padding={{ left: 'small' }} font={{ size: 'small' }}>
|
||||
{getString('importSpace.content')}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
<hr className={css.dividerContainer} />
|
||||
<Container className={css.textContainer} width={'70%'}>
|
||||
<FormInput.Select
|
||||
value={{ value: values.gitProvider, label: values.gitProvider } || providers[0]}
|
||||
name={'gitProvider'}
|
||||
label={getString('importSpace.gitProvider')}
|
||||
items={providers}
|
||||
className={css.selectBox}
|
||||
/>
|
||||
{formik.errors.gitProvider ? (
|
||||
<Text
|
||||
margin={{ top: 'small', bottom: 'small' }}
|
||||
color={Color.RED_500}
|
||||
icon="circle-cross"
|
||||
iconProps={{ color: Color.RED_500 }}>
|
||||
{formik.errors.gitProvider}
|
||||
</Text>
|
||||
) : null}
|
||||
<Layout.Horizontal flex>
|
||||
{getString('importSpace.authorization')}
|
||||
<Container padding={{ left: 'small' }} width={'100%'}>
|
||||
<hr className={css.dividerContainer} />
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
<FormInput.Text
|
||||
name="username"
|
||||
label={getString('userName')}
|
||||
placeholder={getString('importRepo.userPlaceholder')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'spaceUserTextField'
|
||||
}}
|
||||
/>
|
||||
{formik.errors.username ? (
|
||||
<Text
|
||||
margin={{ top: 'small', bottom: 'small' }}
|
||||
color={Color.RED_500}
|
||||
icon="circle-cross"
|
||||
iconProps={{ color: Color.RED_500 }}>
|
||||
{formik.errors.username}
|
||||
</Text>
|
||||
) : null}
|
||||
<FormInput.Text
|
||||
name="password"
|
||||
label={getString('importRepo.passToken')}
|
||||
placeholder={getString('importRepo.passwordPlaceholder')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'spacePasswordTextField'
|
||||
}}
|
||||
inputGroup={{ type: 'password' }}
|
||||
/>
|
||||
{formik.errors.password ? (
|
||||
<Text
|
||||
margin={{ top: 'small', bottom: 'small' }}
|
||||
color={Color.RED_500}
|
||||
icon="circle-cross"
|
||||
iconProps={{ color: Color.RED_500 }}>
|
||||
{formik.errors.password}
|
||||
</Text>
|
||||
) : null}
|
||||
</Container>
|
||||
</>
|
||||
) : null}
|
||||
{step === 1 ? (
|
||||
<>
|
||||
<Layout.Horizontal flex>
|
||||
{/* <Container className={css.detailsLabel}> */}
|
||||
<Text className={css.detailsLabel} font={{ size: 'small' }} flex>
|
||||
{getString('importSpace.details')}
|
||||
</Text>
|
||||
{/* </Container> */}
|
||||
<Container padding={{ left: 'small' }} width={'100%'}>
|
||||
<hr className={css.dividerContainer} />
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
<Container className={css.textContainer} width={'70%'}>
|
||||
<FormInput.Text
|
||||
name="organization"
|
||||
label={
|
||||
formik.values.gitProvider === Organization.GITHUB
|
||||
? getString('importSpace.githubOrg')
|
||||
: getString('importSpace.gitlabGroup')
|
||||
}
|
||||
placeholder={getString('importSpace.orgNamePlaceholder')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'importSpaceOrgName'
|
||||
}}
|
||||
/>
|
||||
{formik.errors.organization ? (
|
||||
<Text
|
||||
margin={{ bottom: 'small' }}
|
||||
color={Color.RED_500}
|
||||
icon="circle-cross"
|
||||
iconProps={{ color: Color.RED_500 }}>
|
||||
{formik.errors.organization}
|
||||
</Text>
|
||||
) : null}
|
||||
<Layout.Horizontal>
|
||||
<Label>{getString('importSpace.importLabel')}</Label>
|
||||
<Icon padding={{ left: 'small' }} className={css.icon} name="code-info" size={16} />
|
||||
</Layout.Horizontal>
|
||||
|
||||
<Container className={css.importContainer} padding={'medium'}>
|
||||
<Layout.Horizontal>
|
||||
<FormInput.CheckBox
|
||||
name="repositories"
|
||||
label={getString('pageTitle.repositories')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'authorization'
|
||||
}}
|
||||
defaultChecked
|
||||
onClick={() => {
|
||||
setAuth(!auth)
|
||||
}}
|
||||
disabled
|
||||
padding={{ right: 'small' }}
|
||||
className={css.checkbox}
|
||||
/>
|
||||
<Container padding={{ left: 'xxxlarge' }}>
|
||||
<FormInput.CheckBox
|
||||
name="pipelines"
|
||||
label={getString('pageTitle.pipelines')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'pipelines'
|
||||
}}
|
||||
onClick={() => {
|
||||
setAuth(!auth)
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
<Container>
|
||||
<hr className={css.dividerContainer} />
|
||||
<FormInput.Text
|
||||
name="name"
|
||||
label={getString('importSpace.spaceName')}
|
||||
placeholder={getString('enterName')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'importSpaceName'
|
||||
}}
|
||||
/>
|
||||
{formik.errors.name ? (
|
||||
<Text
|
||||
margin={{ bottom: 'small' }}
|
||||
color={Color.RED_500}
|
||||
icon="circle-cross"
|
||||
iconProps={{ color: Color.RED_500 }}>
|
||||
{formik.errors.name}
|
||||
</Text>
|
||||
) : null}
|
||||
<FormInput.Text
|
||||
name="description"
|
||||
label={getString('importSpace.description')}
|
||||
placeholder={getString('importSpace.descPlaceholder')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'importSpaceDesc'
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</Container>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<hr className={css.dividerContainer} />
|
||||
|
||||
<Layout.Horizontal
|
||||
spacing="small"
|
||||
padding={{ right: 'xxlarge', bottom: 'large' }}
|
||||
style={{ alignItems: 'center' }}>
|
||||
{step === 1 ? (
|
||||
<Button
|
||||
disabled={buttonLoading}
|
||||
text={
|
||||
buttonLoading ? (
|
||||
<>
|
||||
<Container className={css.loadingIcon} width={93.5} flex={{ alignItems: 'center' }}>
|
||||
<Icon className={css.loadingIcon} name="steps-spinner" size={16} />
|
||||
</Container>
|
||||
</>
|
||||
) : (
|
||||
getString('importSpace.title')
|
||||
)
|
||||
}
|
||||
intent={Intent.PRIMARY}
|
||||
onClick={() => {
|
||||
handleValidationClick()
|
||||
|
||||
if (formik.values.name !== '' || formik.values.organization !== '') {
|
||||
handleImport()
|
||||
setButtonLoading(false)
|
||||
}
|
||||
formik.setErrors({})
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
text={getString('importSpace.next')}
|
||||
intent={Intent.PRIMARY}
|
||||
onClick={() => {
|
||||
handleValidationClick()
|
||||
if (
|
||||
(!formik.errors.gitProvider && formik.touched.gitProvider) ||
|
||||
(!formik.errors.username && formik.touched.username) ||
|
||||
(!formik.errors.password && formik.touched.password)
|
||||
) {
|
||||
formik.setErrors({})
|
||||
setStep(1)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button text={getString('cancel')} minimal onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
</Container>
|
||||
)
|
||||
}}
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImportSpaceForm
|
@ -1,3 +1,127 @@
|
||||
.divider {
|
||||
margin: var(--spacing-medium) 0 var(--spacing-large) 0 !important;
|
||||
}
|
||||
|
||||
.popoverSplit {
|
||||
:global {
|
||||
.bp3-menu {
|
||||
min-width: 248px;
|
||||
max-width: 248px;
|
||||
}
|
||||
.bp3-menu-item {
|
||||
min-width: 240px;
|
||||
max-width: 240px;
|
||||
}
|
||||
.menuItem {
|
||||
max-width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
.test {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.dividerContainer {
|
||||
opacity: 0.2;
|
||||
height: 1px;
|
||||
color: var(--grey-100);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.dividerContainer {
|
||||
opacity: 0.2;
|
||||
height: 1px;
|
||||
color: var(--grey-100);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.halfDividerContainer {
|
||||
opacity: 0.2;
|
||||
height: 1px;
|
||||
color: var(--grey-100);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.textContainer {
|
||||
font-size: var(--font-size-small) !important;
|
||||
--form-input-font-size: var(--font-size-small) !important;
|
||||
|
||||
.selectBox {
|
||||
margin-bottom: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.importContainer {
|
||||
background: var(--grey-50) !important;
|
||||
border: 1px solid var(--grey-200) !important;
|
||||
border-radius: 4px;
|
||||
|
||||
:global {
|
||||
.bp3-form-group {
|
||||
margin: unset !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loadingIcon {
|
||||
fill: var(--grey-0) !important;
|
||||
color: var(--grey-0) !important;
|
||||
|
||||
:global {
|
||||
.bp3-icon {
|
||||
padding-left: 45px !important;
|
||||
fill: var(--grey-0) !important;
|
||||
color: var(--grey-0) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detailsLabel {
|
||||
white-space: nowrap !important;
|
||||
max-width: 155px !important;
|
||||
color: var(--grey-600) !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
> svg {
|
||||
fill: var(--primary-7) !important;
|
||||
|
||||
> path {
|
||||
fill: var(--primary-7) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
:global {
|
||||
.Tooltip--acenter {
|
||||
opacity: 0.7 !important;
|
||||
}
|
||||
.bp3-control-indicator {
|
||||
background: var(--primary-7) !important;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popoverSpace {
|
||||
position: relative;
|
||||
left: 33px;
|
||||
:global {
|
||||
.bp3-menu {
|
||||
min-width: 162px;
|
||||
max-width: 162px;
|
||||
}
|
||||
.bp3-menu-item {
|
||||
min-width: 153px;
|
||||
max-width: 153px;
|
||||
}
|
||||
.menuItem {
|
||||
max-width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,16 @@
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const checkbox: string
|
||||
export declare const detailsLabel: string
|
||||
export declare const divider: string
|
||||
export declare const dividerContainer: string
|
||||
export declare const halfDividerContainer: string
|
||||
export declare const icon: string
|
||||
export declare const importContainer: string
|
||||
export declare const loadingIcon: string
|
||||
export declare const menuItem: string
|
||||
export declare const popoverSpace: string
|
||||
export declare const popoverSplit: string
|
||||
export declare const selectBox: string
|
||||
export declare const test: string
|
||||
export declare const textContainer: string
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Dialog, Intent } from '@blueprintjs/core'
|
||||
import React, { useState } from 'react'
|
||||
import { Dialog, Intent, PopoverPosition, Menu } from '@blueprintjs/core'
|
||||
import * as yup from 'yup'
|
||||
import {
|
||||
Button,
|
||||
@ -11,7 +11,9 @@ import {
|
||||
FormikForm,
|
||||
Heading,
|
||||
useToaster,
|
||||
FormInput
|
||||
FormInput,
|
||||
ButtonVariation,
|
||||
SplitButton
|
||||
} from '@harnessio/uicore'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { FontVariation } from '@harnessio/design-system'
|
||||
@ -19,16 +21,19 @@ import { useMutate } from 'restful-react'
|
||||
import { get } from 'lodash-es'
|
||||
import { useModalHook } from 'hooks/useModalHook'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { getErrorMessage, REGEX_VALID_REPO_NAME } from 'utils/Utils'
|
||||
import { getErrorMessage, permissionProps, REGEX_VALID_REPO_NAME } from 'utils/Utils'
|
||||
import type { TypesSpace, OpenapiCreateSpaceRequest } from 'services/code'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { ImportSpaceFormData, SpaceCreationType } from 'utils/GitUtils'
|
||||
import ImportSpaceForm from './ImportSpaceForm/ImportSpaceForm'
|
||||
import css from './NewSpaceModalButton.module.scss'
|
||||
|
||||
enum RepoVisibility {
|
||||
PUBLIC = 'public',
|
||||
PRIVATE = 'private'
|
||||
}
|
||||
|
||||
interface RepoFormData {
|
||||
interface SpaceFormData {
|
||||
name: string
|
||||
description: string
|
||||
license: string
|
||||
@ -38,7 +43,7 @@ interface RepoFormData {
|
||||
isPublic: RepoVisibility
|
||||
}
|
||||
|
||||
const formInitialValues: RepoFormData = {
|
||||
const formInitialValues: SpaceFormData = {
|
||||
name: '',
|
||||
description: '',
|
||||
license: '',
|
||||
@ -48,13 +53,15 @@ const formInitialValues: RepoFormData = {
|
||||
isPublic: RepoVisibility.PRIVATE
|
||||
}
|
||||
|
||||
export interface NewSpaceModalButtonProps extends Omit<ButtonProps, 'onClick'> {
|
||||
export interface NewSpaceModalButtonProps extends Omit<ButtonProps, 'onClick' | 'onSubmit'> {
|
||||
space: string
|
||||
modalTitle: string
|
||||
submitButtonTitle?: string
|
||||
cancelButtonTitle?: string
|
||||
onRefetch: () => void
|
||||
handleNavigation?: (value: string) => void
|
||||
onSubmit: (data: TypesSpace) => void
|
||||
fromSpace?: boolean
|
||||
}
|
||||
export interface OpenapiCreateSpaceRequestExtended extends OpenapiCreateSpaceRequest {
|
||||
parent_id?: number
|
||||
@ -67,6 +74,8 @@ export const NewSpaceModalButton: React.FC<NewSpaceModalButtonProps> = ({
|
||||
cancelButtonTitle,
|
||||
onRefetch,
|
||||
handleNavigation,
|
||||
onSubmit,
|
||||
fromSpace = false,
|
||||
...props
|
||||
}) => {
|
||||
const ModalComponent: React.FC = () => {
|
||||
@ -78,10 +87,14 @@ export const NewSpaceModalButton: React.FC<NewSpaceModalButtonProps> = ({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/spaces`
|
||||
})
|
||||
const { mutate: importSpace, loading: submitImportLoading } = useMutate<TypesSpace>({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/spaces/import`
|
||||
})
|
||||
|
||||
const loading = submitLoading
|
||||
const loading = submitLoading || submitImportLoading
|
||||
|
||||
const handleSubmit = (formData: RepoFormData) => {
|
||||
const handleSubmit = (formData: SpaceFormData) => {
|
||||
try {
|
||||
const payload: OpenapiCreateSpaceRequestExtended = {
|
||||
description: get(formData, 'description', '').trim(),
|
||||
@ -103,74 +116,179 @@ export const NewSpaceModalButton: React.FC<NewSpaceModalButtonProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const handleImportSubmit = async (formData: ImportSpaceFormData) => {
|
||||
try {
|
||||
const importPayload = {
|
||||
description: formData.description || '',
|
||||
uid: formData.name,
|
||||
provider: {
|
||||
type: formData.gitProvider.toLowerCase(),
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
},
|
||||
provider_space: formData.organization
|
||||
}
|
||||
await importSpace(importPayload)
|
||||
.then(response => {
|
||||
hideModal()
|
||||
onSubmit(response)
|
||||
onRefetch()
|
||||
})
|
||||
.catch(_error => {
|
||||
showError(getErrorMessage(_error), 0, getString('failedToImportSpace'))
|
||||
})
|
||||
} catch (exception) {
|
||||
showError(getErrorMessage(exception), 0, getString('failedToImportSpace'))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
enforceFocus={false}
|
||||
onClose={hideModal}
|
||||
enforceFocus={false}
|
||||
title={''}
|
||||
style={{ width: 700, maxHeight: '95vh', overflow: 'auto' }}>
|
||||
style={{
|
||||
width: spaceOption.type === SpaceCreationType.IMPORT ? 610 : 700,
|
||||
maxHeight: '95vh',
|
||||
overflow: 'auto'
|
||||
}}>
|
||||
<Layout.Vertical
|
||||
padding={{ left: 'xxlarge' }}
|
||||
style={{ height: '100%' }}
|
||||
data-testid="add-target-to-flag-modal">
|
||||
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
|
||||
{modalTitle}
|
||||
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'large' }}>
|
||||
{spaceOption.type === SpaceCreationType.IMPORT ? getString('importSpace.title') : modalTitle}
|
||||
</Heading>
|
||||
|
||||
<Container margin={{ right: 'xxlarge' }}>
|
||||
<Formik
|
||||
initialValues={formInitialValues}
|
||||
formName="editVariations"
|
||||
enableReinitialize={true}
|
||||
validationSchema={yup.object().shape({
|
||||
name: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required()
|
||||
.matches(REGEX_VALID_REPO_NAME, getString('validation.spaceNamePatternIsNotValid'))
|
||||
})}
|
||||
validateOnChange
|
||||
validateOnBlur
|
||||
onSubmit={handleSubmit}>
|
||||
<FormikForm>
|
||||
<FormInput.Text
|
||||
name="name"
|
||||
label={getString('name')}
|
||||
placeholder={getString('enterName')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'spaceNameTextField'
|
||||
}}
|
||||
inputGroup={{ autoFocus: true }}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name="description"
|
||||
label={getString('description')}
|
||||
placeholder={getString('enterDescription')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'spaceDescriptionTextField'
|
||||
}}
|
||||
/>
|
||||
{spaceOption.type === SpaceCreationType.IMPORT ? (
|
||||
<ImportSpaceForm hideModal={hideModal} handleSubmit={handleImportSubmit} loading={false} />
|
||||
) : (
|
||||
<Formik
|
||||
initialValues={formInitialValues}
|
||||
formName="editVariations"
|
||||
enableReinitialize={true}
|
||||
validationSchema={yup.object().shape({
|
||||
name: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required()
|
||||
.matches(REGEX_VALID_REPO_NAME, getString('validation.spaceNamePatternIsNotValid'))
|
||||
})}
|
||||
validateOnChange
|
||||
validateOnBlur
|
||||
onSubmit={handleSubmit}>
|
||||
<FormikForm>
|
||||
<FormInput.Text
|
||||
name="name"
|
||||
label={getString('name')}
|
||||
placeholder={getString('enterName')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'spaceNameTextField'
|
||||
}}
|
||||
inputGroup={{ autoFocus: true }}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name="description"
|
||||
label={getString('description')}
|
||||
placeholder={getString('enterDescription')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'spaceDescriptionTextField'
|
||||
}}
|
||||
/>
|
||||
|
||||
<Layout.Horizontal
|
||||
spacing="small"
|
||||
padding={{ right: 'xxlarge', top: 'xxxlarge', bottom: 'large' }}
|
||||
style={{ alignItems: 'center' }}>
|
||||
<Button type="submit" text={getString('createSpace')} intent={Intent.PRIMARY} disabled={loading} />
|
||||
<Button text={cancelButtonTitle || getString('cancel')} minimal onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
<Layout.Horizontal
|
||||
spacing="small"
|
||||
padding={{ right: 'xxlarge', top: 'xxxlarge', bottom: 'large' }}
|
||||
style={{ alignItems: 'center' }}>
|
||||
<Button type="submit" text={getString('createSpace')} intent={Intent.PRIMARY} disabled={loading} />
|
||||
<Button text={cancelButtonTitle || getString('cancel')} minimal onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
)}
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
const [openModal, hideModal] = useModalHook(ModalComponent)
|
||||
const { getString } = useStrings()
|
||||
|
||||
return <Button onClick={openModal} {...props} />
|
||||
const spaceCreateOptions: SpaceCreationOption[] = [
|
||||
{
|
||||
type: SpaceCreationType.CREATE,
|
||||
title: fromSpace ? getString('newSpace') : getString('createSpace'),
|
||||
desc: getString('importSpace.createNewSpace')
|
||||
},
|
||||
{
|
||||
type: SpaceCreationType.IMPORT,
|
||||
title: getString('importSpace.title'),
|
||||
desc: getString('importSpace.title')
|
||||
}
|
||||
]
|
||||
const [spaceOption, setSpaceOption] = useState<SpaceCreationOption>(spaceCreateOptions[0])
|
||||
|
||||
const [openModal, hideModal] = useModalHook(ModalComponent, [onSubmit, spaceOption])
|
||||
const { standalone } = useAppContext()
|
||||
const { hooks } = useAppContext()
|
||||
const permResult = hooks?.usePermissionTranslate?.(
|
||||
{
|
||||
resource: {
|
||||
resourceType: 'CODE_REPOSITORY'
|
||||
},
|
||||
permissions: ['code_repo_push']
|
||||
},
|
||||
[space]
|
||||
)
|
||||
return (
|
||||
<SplitButton
|
||||
{...props}
|
||||
loading={false}
|
||||
text={spaceOption.title}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
popoverProps={{
|
||||
interactionKind: 'click',
|
||||
usePortal: true,
|
||||
popoverClassName: fromSpace ? css.popoverSpace : css.popoverSplit,
|
||||
position: PopoverPosition.BOTTOM_RIGHT,
|
||||
transitionDuration: 1000
|
||||
}}
|
||||
icon={'plus'}
|
||||
{...permissionProps(permResult, standalone)}
|
||||
onClick={() => {
|
||||
openModal()
|
||||
}}>
|
||||
{spaceCreateOptions.map(option => {
|
||||
return (
|
||||
<Container key={`import_space_container_${option.type}`}>
|
||||
<Menu.Item
|
||||
key={`import_space_${option.type}`}
|
||||
className={css.menuItem}
|
||||
text={
|
||||
<>
|
||||
<p>{option.desc}</p>
|
||||
</>
|
||||
}
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
setSpaceOption(option)
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
})}
|
||||
</SplitButton>
|
||||
)
|
||||
}
|
||||
|
||||
interface SpaceCreationOption {
|
||||
type: SpaceCreationType
|
||||
title: string
|
||||
desc: string
|
||||
}
|
||||
|
@ -92,6 +92,11 @@ export const SpaceSelector: React.FC<SpaceSelectorProps> = ({ onSelect }) => {
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
icon="plus"
|
||||
onRefetch={voidFn(refetch)}
|
||||
onSubmit={spaceData => {
|
||||
history.push(routes.toCODERepositories({ space: spaceData.path as string }))
|
||||
setOpened(false)
|
||||
}}
|
||||
fromSpace={true}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -213,6 +218,10 @@ export const SpaceSelector: React.FC<SpaceSelectorProps> = ({ onSelect }) => {
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
icon="plus"
|
||||
onRefetch={voidFn(refetch)}
|
||||
onSubmit={spaceData => {
|
||||
history.push(routes.toCODERepositories({ space: spaceData.path as string }))
|
||||
}}
|
||||
fromSpace={true}
|
||||
/>
|
||||
}
|
||||
message={<Text font={{ variation: FontVariation.H4 }}> {getString('emptySpaceText')}</Text>}
|
||||
|
@ -105,6 +105,7 @@ export interface StringsMap {
|
||||
createBranchFromBranch: string
|
||||
createBranchFromTag: string
|
||||
createFile: string
|
||||
createNewRepo: string
|
||||
createNewToken: string
|
||||
createNewUser: string
|
||||
createPullRequest: string
|
||||
@ -194,6 +195,7 @@ export interface StringsMap {
|
||||
failedToDeleteBranch: string
|
||||
failedToDeleteWebhook: string
|
||||
failedToFetchFileContent: string
|
||||
failedToImportSpace: string
|
||||
failedToSavePipeline: string
|
||||
fileDeleted: string
|
||||
fileTooLarge: string
|
||||
@ -215,6 +217,40 @@ export interface StringsMap {
|
||||
history: string
|
||||
'homepage.firstStep': string
|
||||
'homepage.welcomeText': string
|
||||
importGitRepo: string
|
||||
importProgress: string
|
||||
'importRepo.failedToImportRepo': string
|
||||
'importRepo.passToken': string
|
||||
'importRepo.passwordPlaceholder': string
|
||||
'importRepo.passwordReq': string
|
||||
'importRepo.reqAuth': string
|
||||
'importRepo.required': string
|
||||
'importRepo.spaceNameReq': string
|
||||
'importRepo.title': string
|
||||
'importRepo.url': string
|
||||
'importRepo.urlPlaceholder': string
|
||||
'importRepo.userPlaceholder': string
|
||||
'importRepo.usernameReq': string
|
||||
'importRepo.validation': string
|
||||
'importSpace.authorization': string
|
||||
'importSpace.content': string
|
||||
'importSpace.createNewSpace': string
|
||||
'importSpace.descPlaceholder': string
|
||||
'importSpace.description': string
|
||||
'importSpace.details': string
|
||||
'importSpace.gitProvider': string
|
||||
'importSpace.githubOrg': string
|
||||
'importSpace.gitlabGroup': string
|
||||
'importSpace.importLabel': string
|
||||
'importSpace.invalidUrl': string
|
||||
'importSpace.next': string
|
||||
'importSpace.orgNamePlaceholder': string
|
||||
'importSpace.orgRequired': string
|
||||
'importSpace.organizationName': string
|
||||
'importSpace.providerRequired': string
|
||||
'importSpace.spaceName': string
|
||||
'importSpace.spaceNameRequired': string
|
||||
'importSpace.title': string
|
||||
in: string
|
||||
inactiveBranches: string
|
||||
isRequired: string
|
||||
|
@ -589,6 +589,7 @@ spaceMemberships:
|
||||
memberUpdated: Member updated successfully.
|
||||
memberAdded: Member added successfully.
|
||||
failedToCreateSpace: Failed to create Space. Please try again.
|
||||
failedToImportSpace: Failed to import Space. Please try again.
|
||||
failedToCreatePipeline: Failed to create Pipeline. Please try again.
|
||||
failedToSavePipeline: Failed to save Pipeline. Please try again.
|
||||
enterName: Enter the name
|
||||
@ -692,3 +693,40 @@ plugins:
|
||||
title: Plugins
|
||||
addAPlugin: Add a {{category}} plugin
|
||||
stepLabel: step
|
||||
createNewRepo: Create New repository
|
||||
importGitRepo: Import Git Repository
|
||||
importRepo:
|
||||
title: Import Repository
|
||||
url: Repository URL
|
||||
urlPlaceholder: Enter the Repository URL
|
||||
reqAuth: Requires Authorization
|
||||
userPlaceholder: Enter Username
|
||||
passwordPlaceholder: Enter Password
|
||||
passToken: Password/Token
|
||||
failedToImportRepo: Failed to import repository. Please try again.
|
||||
validation: Invalid GitHub or GitLab URL
|
||||
required: Repository URL is required
|
||||
spaceNameReq: Enter a name for the new space
|
||||
usernameReq: Username is required
|
||||
passwordReq: Password is required
|
||||
importSpace:
|
||||
title: Import Space
|
||||
createNewSpace: Create New Space
|
||||
authorization: Authorization
|
||||
content: Import a GitLab Group or a GitHub Org to a new Space in Gitness. Entities at the top level will be imported to the space.
|
||||
next: Next step
|
||||
details: Details of target to import
|
||||
organizationName: Organization Name
|
||||
orgNamePlaceholder: Enter the org name here
|
||||
spaceName: Space Name
|
||||
description: Description (optional)
|
||||
descPlaceholder: Enter the description
|
||||
importLabel: What to import
|
||||
providerRequired: Git Provider is required
|
||||
orgRequired: Organization name is required
|
||||
spaceNameRequired: Space name is required
|
||||
gitProvider: Git Provider
|
||||
invalidUrl: Invalid GitHub or GitLab URL
|
||||
githubOrg: GitHub Organization Name
|
||||
gitlabGroup: GitLab Group Name
|
||||
importProgress: 'Import in progress...'
|
||||
|
@ -10,3 +10,28 @@
|
||||
height: 100vh;
|
||||
padding-top: 26% !important;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
:global {
|
||||
.bp3-button-text {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bigButton {
|
||||
:global {
|
||||
.SplitButton--splitButton {
|
||||
> .bp3-button {
|
||||
.bp3-button-text {
|
||||
font-size: 16px !important;
|
||||
--font-size: 16px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bp3-icon-chevron-down {
|
||||
left: -7px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
web/src/pages/Home/Home.module.scss.d.ts
vendored
2
web/src/pages/Home/Home.module.scss.d.ts
vendored
@ -1,5 +1,7 @@
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const bigButton: string
|
||||
export declare const buttonContainer: string
|
||||
export declare const container: string
|
||||
export declare const main: string
|
||||
export declare const spaceContainer: string
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { ButtonVariation, Container, Layout, PageBody, Text } from '@harnessio/uicore'
|
||||
import { ButtonVariation, ButtonSize, Container, Layout, PageBody, Text } from '@harnessio/uicore'
|
||||
import { FontVariation } from '@harnessio/design-system'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
@ -21,17 +21,20 @@ export default function Home() {
|
||||
|
||||
const NewSpaceButton = (
|
||||
<NewSpaceModalButton
|
||||
size={ButtonSize.LARGE}
|
||||
className={css.bigButton}
|
||||
space={space}
|
||||
modalTitle={getString('createASpace')}
|
||||
text={getString('newSpace')}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
icon="plus"
|
||||
width={173}
|
||||
height={48}
|
||||
onRefetch={noop}
|
||||
handleNavigation={spaceName => {
|
||||
history.push(routes.toCODERepositories({ space: spaceName }))
|
||||
}}
|
||||
onSubmit={data => {
|
||||
history.push(routes.toCODERepositories({ space: data.path as string }))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
@ -51,7 +54,7 @@ export default function Home() {
|
||||
})}
|
||||
</Text>
|
||||
<Text font={{ variation: FontVariation.BODY1 }}>{getString('homepage.firstStep')} </Text>
|
||||
<Container padding={{ top: 'large' }} flex={{ justifyContent: 'center' }}>
|
||||
<Container className={css.buttonContainer} padding={{ top: 'large' }} flex={{ justifyContent: 'center' }}>
|
||||
{NewSpaceButton}
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
|
@ -39,13 +39,26 @@
|
||||
|
||||
.row {
|
||||
height: 80px;
|
||||
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
|
||||
overflow: hidden;
|
||||
|
||||
&.noDesc > div {
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
.rowDisable {
|
||||
background-color: var(--grey-50) !important;
|
||||
cursor: auto !important;
|
||||
.repoName {
|
||||
color: var(--grey-400) !important;
|
||||
}
|
||||
.desc {
|
||||
color: var(--grey-300) !important;
|
||||
}
|
||||
}
|
||||
.rowDisable:hover {
|
||||
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16) !important;
|
||||
border: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nameContainer {
|
||||
@ -102,3 +115,7 @@
|
||||
padding-top: var(--spacing-xsmall) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
@ -8,8 +8,10 @@ export declare const name: string
|
||||
export declare const nameContainer: string
|
||||
export declare const noDesc: string
|
||||
export declare const pinned: string
|
||||
export declare const progressBar: string
|
||||
export declare const repoName: string
|
||||
export declare const repoScope: string
|
||||
export declare const row: string
|
||||
export declare const rowDisable: string
|
||||
export declare const table: string
|
||||
export declare const withError: string
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
TableV2 as Table,
|
||||
Text
|
||||
} from '@harnessio/uicore'
|
||||
import { ProgressBar, Intent } from '@blueprintjs/core'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { Color } from '@harnessio/design-system'
|
||||
import type { CellProps, Column } from 'react-table'
|
||||
@ -31,6 +32,10 @@ import { NoResultCard } from 'components/NoResultCard/NoResultCard'
|
||||
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||
import noRepoImage from './no-repo.svg'
|
||||
import css from './RepositoriesListing.module.scss'
|
||||
import useSpaceSSE from 'hooks/useSpaceSSE'
|
||||
interface TypesRepoExtended extends TypesRepository {
|
||||
importing?: boolean
|
||||
}
|
||||
|
||||
export default function RepositoriesListing() {
|
||||
const { getString } = useStrings()
|
||||
@ -56,6 +61,18 @@ export default function RepositoriesListing() {
|
||||
queryParams: { page, limit: LIST_FETCHING_LIMIT, query: searchTerm },
|
||||
debounce: 500
|
||||
})
|
||||
useSpaceSSE({
|
||||
space,
|
||||
events: ['repository_import_completed'],
|
||||
onEvent: data => {
|
||||
// should I include pipeline id here? what if a new pipeline is created? coould check for ids that are higher than the lowest id on the page?
|
||||
if (repositories?.some(repository => repository.id === data?.id && repository.parent_id === data?.parent_id)) {
|
||||
//TODO - revisit full refresh - can I use the message to update the execution?
|
||||
console.log('here')
|
||||
refetch()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setSearchTerm(undefined)
|
||||
@ -64,12 +81,13 @@ export default function RepositoriesListing() {
|
||||
}
|
||||
}, [space, setPage]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const columns: Column<TypesRepository>[] = useMemo(
|
||||
const columns: Column<TypesRepoExtended>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: getString('repos.name'),
|
||||
width: 'calc(100% - 180px)',
|
||||
Cell: ({ row }: CellProps<TypesRepository>) => {
|
||||
|
||||
Cell: ({ row }: CellProps<TypesRepoExtended>) => {
|
||||
const record = row.original
|
||||
return (
|
||||
<Container className={css.nameContainer}>
|
||||
@ -78,10 +96,16 @@ export default function RepositoriesListing() {
|
||||
<Text className={css.repoName} width={nameTextWidth} lineClamp={2}>
|
||||
<Keywords value={searchTerm}>{record.uid}</Keywords>
|
||||
</Text>
|
||||
{record.description && (
|
||||
{record.importing ? (
|
||||
<Text className={css.desc} width={nameTextWidth} lineClamp={1}>
|
||||
{record.description}
|
||||
{getString('importProgress')}
|
||||
</Text>
|
||||
) : (
|
||||
record.description && (
|
||||
<Text className={css.desc} width={nameTextWidth} lineClamp={1}>
|
||||
{record.description}
|
||||
</Text>
|
||||
)
|
||||
)}
|
||||
</Layout.Vertical>
|
||||
</Layout.Horizontal>
|
||||
@ -92,8 +116,12 @@ export default function RepositoriesListing() {
|
||||
{
|
||||
Header: getString('repos.updated'),
|
||||
width: '180px',
|
||||
Cell: ({ row }: CellProps<TypesRepository>) => {
|
||||
return (
|
||||
Cell: ({ row }: CellProps<TypesRepoExtended>) => {
|
||||
return row.original.importing ? (
|
||||
<Layout.Horizontal style={{ alignItems: 'center' }} padding={{ right: 'large' }}>
|
||||
<ProgressBar intent={Intent.PRIMARY} className={css.progressBar} />
|
||||
</Layout.Horizontal>
|
||||
) : (
|
||||
<Layout.Horizontal style={{ alignItems: 'center' }}>
|
||||
<Text color={Color.BLACK} lineClamp={1} rightIconProps={{ size: 10 }} width={120}>
|
||||
{formatDate(row.original.updated as number)}
|
||||
@ -107,6 +135,7 @@ export default function RepositoriesListing() {
|
||||
],
|
||||
[nameTextWidth, getString, searchTerm]
|
||||
)
|
||||
|
||||
const onResize = useCallback(() => {
|
||||
if (rowContainerRef.current) {
|
||||
setNameTextWidth((rowContainerRef.current.closest('div[role="cell"]') as HTMLDivElement)?.offsetWidth - 100)
|
||||
@ -118,8 +147,13 @@ export default function RepositoriesListing() {
|
||||
modalTitle={getString('createARepo')}
|
||||
text={getString('newRepo')}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
icon="plus"
|
||||
onSubmit={repoInfo => history.push(routes.toCODERepository({ repoPath: repoInfo.path as string }))}
|
||||
onSubmit={repoInfo => {
|
||||
if (repoInfo.importing) {
|
||||
refetch()
|
||||
} else {
|
||||
history.push(routes.toCODERepository({ repoPath: repoInfo.path as string }))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -155,12 +189,18 @@ export default function RepositoriesListing() {
|
||||
|
||||
<Container margin={{ top: 'medium' }}>
|
||||
{!!repositories?.length && (
|
||||
<Table<TypesRepository>
|
||||
<Table<TypesRepoExtended>
|
||||
className={css.table}
|
||||
columns={columns}
|
||||
data={repositories || []}
|
||||
onRowClick={repoInfo => history.push(routes.toCODERepository({ repoPath: repoInfo.path as string }))}
|
||||
getRowClassName={row => cx(css.row, !row.original.description && css.noDesc)}
|
||||
onRowClick={repoInfo => {
|
||||
return repoInfo.importing
|
||||
? undefined
|
||||
: history.push(routes.toCODERepository({ repoPath: repoInfo.path as string }))
|
||||
}}
|
||||
getRowClassName={row =>
|
||||
cx(css.row, !row.original.description && css.noDesc, row.original.importing && css.rowDisable)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -21,6 +21,47 @@ export interface GitInfoProps {
|
||||
commits: TypesCommit[]
|
||||
pullRequestMetadata: TypesPullReq
|
||||
}
|
||||
export interface RepoFormData {
|
||||
name: string
|
||||
description: string
|
||||
license: string
|
||||
defaultBranch: string
|
||||
gitignore: string
|
||||
addReadme: boolean
|
||||
isPublic: RepoVisibility
|
||||
}
|
||||
export interface ImportFormData {
|
||||
repoUrl: string
|
||||
username: string
|
||||
password: string
|
||||
name: string
|
||||
description: string
|
||||
isPublic: RepoVisibility
|
||||
}
|
||||
|
||||
export interface ImportSpaceFormData {
|
||||
gitProvider: string
|
||||
username: string
|
||||
password: string
|
||||
name: string
|
||||
description: string
|
||||
organization: string
|
||||
}
|
||||
|
||||
export enum RepoVisibility {
|
||||
PUBLIC = 'public',
|
||||
PRIVATE = 'private'
|
||||
}
|
||||
|
||||
export enum RepoCreationType {
|
||||
IMPORT = 'import',
|
||||
CREATE = 'create'
|
||||
}
|
||||
|
||||
export enum SpaceCreationType {
|
||||
IMPORT = 'import',
|
||||
CREATE = 'create'
|
||||
}
|
||||
|
||||
export enum GitContentType {
|
||||
FILE = 'file',
|
||||
@ -101,6 +142,11 @@ export const CodeIcon = {
|
||||
ChecksSuccess: 'success-tick' as IconName
|
||||
}
|
||||
|
||||
export enum Organization {
|
||||
GITHUB = 'Github',
|
||||
GITLAB = 'Gitlab'
|
||||
}
|
||||
|
||||
export const REFS_TAGS_PREFIX = 'refs/tags/'
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
@ -168,3 +214,17 @@ export const decodeGitContent = (content = '') => {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export const parseUrl = (url: string) => {
|
||||
const pattern = /^(https?:\/\/(?:www\.)?(github|gitlab)\.com\/([^/]+\/[^/]+))/
|
||||
const match = url.match(pattern)
|
||||
|
||||
if (match) {
|
||||
const provider = match[2]
|
||||
const fullRepo = match[3]
|
||||
const repoName = match[3].split('/')[1]
|
||||
return { provider, fullRepo, repoName }
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user