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 {
|
.divider {
|
||||||
margin: var(--spacing-medium) 0 var(--spacing-large) 0 !important;
|
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 */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
export declare const divider: string
|
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 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 * as yup from 'yup'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -15,7 +24,8 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
ButtonVariation,
|
ButtonVariation,
|
||||||
ButtonSize,
|
ButtonSize,
|
||||||
TextInput
|
TextInput,
|
||||||
|
SplitButton
|
||||||
} from '@harnessio/uicore'
|
} from '@harnessio/uicore'
|
||||||
import { Icon } from '@harnessio/icons'
|
import { Icon } from '@harnessio/icons'
|
||||||
import { FontVariation } from '@harnessio/design-system'
|
import { FontVariation } from '@harnessio/design-system'
|
||||||
@ -30,26 +40,19 @@ import {
|
|||||||
REGEX_VALID_REPO_NAME,
|
REGEX_VALID_REPO_NAME,
|
||||||
SUGGESTED_BRANCH_NAMES
|
SUGGESTED_BRANCH_NAMES
|
||||||
} from 'utils/Utils'
|
} 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 type { TypesRepository, OpenapiCreateRepositoryRequest } from 'services/code'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
|
import ImportForm from './ImportForm/ImportForm'
|
||||||
import css from './NewRepoModalButton.module.scss'
|
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 = {
|
const formInitialValues: RepoFormData = {
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
@ -90,6 +93,15 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
|||||||
space_path: space
|
space_path: space
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const { mutate: importRepo, loading: importRepoLoading } = useMutate<TypesRepository>({
|
||||||
|
verb: 'POST',
|
||||||
|
path: `/api/v1/repos/import`,
|
||||||
|
queryParams: standalone
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
space_path: space
|
||||||
|
}
|
||||||
|
})
|
||||||
const {
|
const {
|
||||||
data: gitignores,
|
data: gitignores,
|
||||||
loading: gitIgnoreLoading,
|
loading: gitIgnoreLoading,
|
||||||
@ -100,14 +112,13 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
|||||||
loading: licenseLoading,
|
loading: licenseLoading,
|
||||||
error: licenseError
|
error: licenseError
|
||||||
} = useGet({ path: '/api/v1/resources/license' })
|
} = useGet({ path: '/api/v1/resources/license' })
|
||||||
const loading = submitLoading || gitIgnoreLoading || licenseLoading
|
const loading = submitLoading || gitIgnoreLoading || licenseLoading || importRepoLoading
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gitIgnoreError || licenseError) {
|
if (gitIgnoreError || licenseError) {
|
||||||
showError(getErrorMessage(gitIgnoreError || licenseError), 0)
|
showError(getErrorMessage(gitIgnoreError || licenseError), 0)
|
||||||
}
|
}
|
||||||
}, [gitIgnoreError, licenseError, showError])
|
}, [gitIgnoreError, licenseError, showError])
|
||||||
|
|
||||||
const handleSubmit = (formData: RepoFormData) => {
|
const handleSubmit = (formData: RepoFormData) => {
|
||||||
try {
|
try {
|
||||||
const payload: OpenapiCreateRepositoryRequest = {
|
const payload: OpenapiCreateRepositoryRequest = {
|
||||||
@ -132,7 +143,24 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
|||||||
showError(getErrorMessage(exception), 0, getString('failedToCreateRepo'))
|
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 (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
isOpen
|
isOpen
|
||||||
@ -145,147 +173,166 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
|||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
data-testid="add-target-to-flag-modal">
|
data-testid="add-target-to-flag-modal">
|
||||||
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
|
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
|
||||||
{modalTitle}
|
{repoOption.type === RepoCreationType.IMPORT ? getString('importRepo.title') : modalTitle}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<Container margin={{ right: 'xxlarge' }}>
|
<Container margin={{ right: 'xxlarge' }}>
|
||||||
<Formik
|
{repoOption.type === RepoCreationType.IMPORT ? (
|
||||||
initialValues={formInitialValues}
|
<ImportForm hideModal={hideModal} handleSubmit={handleImportSubmit} loading={false} />
|
||||||
formName="editVariations"
|
) : (
|
||||||
enableReinitialize={true}
|
<Formik
|
||||||
validationSchema={yup.object().shape({
|
initialValues={formInitialValues}
|
||||||
name: yup
|
formName="editVariations"
|
||||||
.string()
|
enableReinitialize={true}
|
||||||
.trim()
|
validationSchema={yup.object().shape({
|
||||||
.required()
|
name: yup
|
||||||
.matches(REGEX_VALID_REPO_NAME, getString('validation.repoNamePatternIsNotValid'))
|
.string()
|
||||||
})}
|
.trim()
|
||||||
validateOnChange
|
.required()
|
||||||
validateOnBlur
|
.matches(REGEX_VALID_REPO_NAME, getString('validation.repoNamePatternIsNotValid'))
|
||||||
onSubmit={handleSubmit}>
|
})}
|
||||||
<FormikForm>
|
validateOnChange
|
||||||
<FormInput.Text
|
validateOnBlur
|
||||||
name="name"
|
onSubmit={handleSubmit}>
|
||||||
label={getString('name')}
|
<FormikForm>
|
||||||
placeholder={getString('enterRepoName')}
|
<FormInput.Text
|
||||||
tooltipProps={{
|
name="name"
|
||||||
dataTooltipId: 'repositoryNameTextField'
|
label={getString('name')}
|
||||||
}}
|
placeholder={getString('enterRepoName')}
|
||||||
inputGroup={{ autoFocus: true }}
|
tooltipProps={{
|
||||||
/>
|
dataTooltipId: 'repositoryNameTextField'
|
||||||
<FormInput.Text
|
}}
|
||||||
name="description"
|
inputGroup={{ autoFocus: true }}
|
||||||
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.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
|
<FormInput.Select
|
||||||
name="license"
|
name="license"
|
||||||
label={getString('addLicense')}
|
label={getString('addLicense')}
|
||||||
placeholder={getString('none')}
|
placeholder={getString('none')}
|
||||||
items={licences || []}
|
items={licences || []}
|
||||||
usePortal
|
usePortal
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormInput.Select
|
<FormInput.Select
|
||||||
name="gitignore"
|
name="gitignore"
|
||||||
label={getString('addGitIgnore')}
|
label={getString('addGitIgnore')}
|
||||||
placeholder={getString('none')}
|
placeholder={getString('none')}
|
||||||
items={(gitignores || []).map((entry: string) => ({ label: entry, value: entry }))}
|
items={(gitignores || []).map((entry: string) => ({ label: entry, value: entry }))}
|
||||||
usePortal
|
usePortal
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormInput.CheckBox
|
<FormInput.CheckBox
|
||||||
name="addReadme"
|
name="addReadme"
|
||||||
label={getString('addReadMe')}
|
label={getString('addReadMe')}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
dataTooltipId: 'addReadMe'
|
dataTooltipId: 'addReadMe'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Layout.Horizontal
|
<Layout.Horizontal
|
||||||
spacing="small"
|
spacing="small"
|
||||||
padding={{ right: 'xxlarge', top: 'xxxlarge', bottom: 'large' }}
|
padding={{ right: 'xxlarge', top: 'xxxlarge', bottom: 'large' }}
|
||||||
style={{ alignItems: 'center' }}>
|
style={{ alignItems: 'center' }}>
|
||||||
<Button type="submit" text={getString('createRepo')} intent={Intent.PRIMARY} disabled={loading} />
|
<Button type="submit" text={getString('createRepo')} intent={Intent.PRIMARY} disabled={loading} />
|
||||||
<Button text={cancelButtonTitle || getString('cancel')} minimal onClick={hideModal} />
|
<Button text={cancelButtonTitle || getString('cancel')} minimal onClick={hideModal} />
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
|
|
||||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</FormikForm>
|
</FormikForm>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</Layout.Vertical>
|
</Layout.Vertical>
|
||||||
</Dialog>
|
</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 { standalone } = useAppContext()
|
||||||
const { hooks } = useAppContext()
|
const { hooks } = useAppContext()
|
||||||
const permResult = hooks?.usePermissionTranslate?.(
|
const permResult = hooks?.usePermissionTranslate?.(
|
||||||
@ -297,8 +344,48 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
|||||||
},
|
},
|
||||||
[space]
|
[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 {
|
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 {
|
.divider {
|
||||||
margin: var(--spacing-medium) 0 var(--spacing-large) 0 !important;
|
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 */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
|
export declare const checkbox: string
|
||||||
|
export declare const detailsLabel: string
|
||||||
export declare const divider: 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 React, { useState } from 'react'
|
||||||
import { Dialog, Intent } from '@blueprintjs/core'
|
import { Dialog, Intent, PopoverPosition, Menu } from '@blueprintjs/core'
|
||||||
import * as yup from 'yup'
|
import * as yup from 'yup'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -11,7 +11,9 @@ import {
|
|||||||
FormikForm,
|
FormikForm,
|
||||||
Heading,
|
Heading,
|
||||||
useToaster,
|
useToaster,
|
||||||
FormInput
|
FormInput,
|
||||||
|
ButtonVariation,
|
||||||
|
SplitButton
|
||||||
} from '@harnessio/uicore'
|
} from '@harnessio/uicore'
|
||||||
import { Icon } from '@harnessio/icons'
|
import { Icon } from '@harnessio/icons'
|
||||||
import { FontVariation } from '@harnessio/design-system'
|
import { FontVariation } from '@harnessio/design-system'
|
||||||
@ -19,16 +21,19 @@ import { useMutate } from 'restful-react'
|
|||||||
import { get } from 'lodash-es'
|
import { get } from 'lodash-es'
|
||||||
import { useModalHook } from 'hooks/useModalHook'
|
import { useModalHook } from 'hooks/useModalHook'
|
||||||
import { useStrings } from 'framework/strings'
|
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 type { TypesSpace, OpenapiCreateSpaceRequest } from 'services/code'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
|
import { ImportSpaceFormData, SpaceCreationType } from 'utils/GitUtils'
|
||||||
|
import ImportSpaceForm from './ImportSpaceForm/ImportSpaceForm'
|
||||||
|
import css from './NewSpaceModalButton.module.scss'
|
||||||
|
|
||||||
enum RepoVisibility {
|
enum RepoVisibility {
|
||||||
PUBLIC = 'public',
|
PUBLIC = 'public',
|
||||||
PRIVATE = 'private'
|
PRIVATE = 'private'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RepoFormData {
|
interface SpaceFormData {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
license: string
|
license: string
|
||||||
@ -38,7 +43,7 @@ interface RepoFormData {
|
|||||||
isPublic: RepoVisibility
|
isPublic: RepoVisibility
|
||||||
}
|
}
|
||||||
|
|
||||||
const formInitialValues: RepoFormData = {
|
const formInitialValues: SpaceFormData = {
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
license: '',
|
license: '',
|
||||||
@ -48,13 +53,15 @@ const formInitialValues: RepoFormData = {
|
|||||||
isPublic: RepoVisibility.PRIVATE
|
isPublic: RepoVisibility.PRIVATE
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NewSpaceModalButtonProps extends Omit<ButtonProps, 'onClick'> {
|
export interface NewSpaceModalButtonProps extends Omit<ButtonProps, 'onClick' | 'onSubmit'> {
|
||||||
space: string
|
space: string
|
||||||
modalTitle: string
|
modalTitle: string
|
||||||
submitButtonTitle?: string
|
submitButtonTitle?: string
|
||||||
cancelButtonTitle?: string
|
cancelButtonTitle?: string
|
||||||
onRefetch: () => void
|
onRefetch: () => void
|
||||||
handleNavigation?: (value: string) => void
|
handleNavigation?: (value: string) => void
|
||||||
|
onSubmit: (data: TypesSpace) => void
|
||||||
|
fromSpace?: boolean
|
||||||
}
|
}
|
||||||
export interface OpenapiCreateSpaceRequestExtended extends OpenapiCreateSpaceRequest {
|
export interface OpenapiCreateSpaceRequestExtended extends OpenapiCreateSpaceRequest {
|
||||||
parent_id?: number
|
parent_id?: number
|
||||||
@ -67,6 +74,8 @@ export const NewSpaceModalButton: React.FC<NewSpaceModalButtonProps> = ({
|
|||||||
cancelButtonTitle,
|
cancelButtonTitle,
|
||||||
onRefetch,
|
onRefetch,
|
||||||
handleNavigation,
|
handleNavigation,
|
||||||
|
onSubmit,
|
||||||
|
fromSpace = false,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const ModalComponent: React.FC = () => {
|
const ModalComponent: React.FC = () => {
|
||||||
@ -78,10 +87,14 @@ export const NewSpaceModalButton: React.FC<NewSpaceModalButtonProps> = ({
|
|||||||
verb: 'POST',
|
verb: 'POST',
|
||||||
path: `/api/v1/spaces`
|
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 {
|
try {
|
||||||
const payload: OpenapiCreateSpaceRequestExtended = {
|
const payload: OpenapiCreateSpaceRequestExtended = {
|
||||||
description: get(formData, 'description', '').trim(),
|
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 (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
isOpen
|
isOpen
|
||||||
enforceFocus={false}
|
|
||||||
onClose={hideModal}
|
onClose={hideModal}
|
||||||
|
enforceFocus={false}
|
||||||
title={''}
|
title={''}
|
||||||
style={{ width: 700, maxHeight: '95vh', overflow: 'auto' }}>
|
style={{
|
||||||
|
width: spaceOption.type === SpaceCreationType.IMPORT ? 610 : 700,
|
||||||
|
maxHeight: '95vh',
|
||||||
|
overflow: 'auto'
|
||||||
|
}}>
|
||||||
<Layout.Vertical
|
<Layout.Vertical
|
||||||
padding={{ left: 'xxlarge' }}
|
padding={{ left: 'xxlarge' }}
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
data-testid="add-target-to-flag-modal">
|
data-testid="add-target-to-flag-modal">
|
||||||
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
|
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'large' }}>
|
||||||
{modalTitle}
|
{spaceOption.type === SpaceCreationType.IMPORT ? getString('importSpace.title') : modalTitle}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<Container margin={{ right: 'xxlarge' }}>
|
<Container margin={{ right: 'xxlarge' }}>
|
||||||
<Formik
|
{spaceOption.type === SpaceCreationType.IMPORT ? (
|
||||||
initialValues={formInitialValues}
|
<ImportSpaceForm hideModal={hideModal} handleSubmit={handleImportSubmit} loading={false} />
|
||||||
formName="editVariations"
|
) : (
|
||||||
enableReinitialize={true}
|
<Formik
|
||||||
validationSchema={yup.object().shape({
|
initialValues={formInitialValues}
|
||||||
name: yup
|
formName="editVariations"
|
||||||
.string()
|
enableReinitialize={true}
|
||||||
.trim()
|
validationSchema={yup.object().shape({
|
||||||
.required()
|
name: yup
|
||||||
.matches(REGEX_VALID_REPO_NAME, getString('validation.spaceNamePatternIsNotValid'))
|
.string()
|
||||||
})}
|
.trim()
|
||||||
validateOnChange
|
.required()
|
||||||
validateOnBlur
|
.matches(REGEX_VALID_REPO_NAME, getString('validation.spaceNamePatternIsNotValid'))
|
||||||
onSubmit={handleSubmit}>
|
})}
|
||||||
<FormikForm>
|
validateOnChange
|
||||||
<FormInput.Text
|
validateOnBlur
|
||||||
name="name"
|
onSubmit={handleSubmit}>
|
||||||
label={getString('name')}
|
<FormikForm>
|
||||||
placeholder={getString('enterName')}
|
<FormInput.Text
|
||||||
tooltipProps={{
|
name="name"
|
||||||
dataTooltipId: 'spaceNameTextField'
|
label={getString('name')}
|
||||||
}}
|
placeholder={getString('enterName')}
|
||||||
inputGroup={{ autoFocus: true }}
|
tooltipProps={{
|
||||||
/>
|
dataTooltipId: 'spaceNameTextField'
|
||||||
<FormInput.Text
|
}}
|
||||||
name="description"
|
inputGroup={{ autoFocus: true }}
|
||||||
label={getString('description')}
|
/>
|
||||||
placeholder={getString('enterDescription')}
|
<FormInput.Text
|
||||||
tooltipProps={{
|
name="description"
|
||||||
dataTooltipId: 'spaceDescriptionTextField'
|
label={getString('description')}
|
||||||
}}
|
placeholder={getString('enterDescription')}
|
||||||
/>
|
tooltipProps={{
|
||||||
|
dataTooltipId: 'spaceDescriptionTextField'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Layout.Horizontal
|
<Layout.Horizontal
|
||||||
spacing="small"
|
spacing="small"
|
||||||
padding={{ right: 'xxlarge', top: 'xxxlarge', bottom: 'large' }}
|
padding={{ right: 'xxlarge', top: 'xxxlarge', bottom: 'large' }}
|
||||||
style={{ alignItems: 'center' }}>
|
style={{ alignItems: 'center' }}>
|
||||||
<Button type="submit" text={getString('createSpace')} intent={Intent.PRIMARY} disabled={loading} />
|
<Button type="submit" text={getString('createSpace')} intent={Intent.PRIMARY} disabled={loading} />
|
||||||
<Button text={cancelButtonTitle || getString('cancel')} minimal onClick={hideModal} />
|
<Button text={cancelButtonTitle || getString('cancel')} minimal onClick={hideModal} />
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
|
|
||||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</FormikForm>
|
</FormikForm>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</Layout.Vertical>
|
</Layout.Vertical>
|
||||||
</Dialog>
|
</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}
|
variation={ButtonVariation.PRIMARY}
|
||||||
icon="plus"
|
icon="plus"
|
||||||
onRefetch={voidFn(refetch)}
|
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}
|
variation={ButtonVariation.PRIMARY}
|
||||||
icon="plus"
|
icon="plus"
|
||||||
onRefetch={voidFn(refetch)}
|
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>}
|
message={<Text font={{ variation: FontVariation.H4 }}> {getString('emptySpaceText')}</Text>}
|
||||||
|
@ -105,6 +105,7 @@ export interface StringsMap {
|
|||||||
createBranchFromBranch: string
|
createBranchFromBranch: string
|
||||||
createBranchFromTag: string
|
createBranchFromTag: string
|
||||||
createFile: string
|
createFile: string
|
||||||
|
createNewRepo: string
|
||||||
createNewToken: string
|
createNewToken: string
|
||||||
createNewUser: string
|
createNewUser: string
|
||||||
createPullRequest: string
|
createPullRequest: string
|
||||||
@ -194,6 +195,7 @@ export interface StringsMap {
|
|||||||
failedToDeleteBranch: string
|
failedToDeleteBranch: string
|
||||||
failedToDeleteWebhook: string
|
failedToDeleteWebhook: string
|
||||||
failedToFetchFileContent: string
|
failedToFetchFileContent: string
|
||||||
|
failedToImportSpace: string
|
||||||
failedToSavePipeline: string
|
failedToSavePipeline: string
|
||||||
fileDeleted: string
|
fileDeleted: string
|
||||||
fileTooLarge: string
|
fileTooLarge: string
|
||||||
@ -215,6 +217,40 @@ export interface StringsMap {
|
|||||||
history: string
|
history: string
|
||||||
'homepage.firstStep': string
|
'homepage.firstStep': string
|
||||||
'homepage.welcomeText': 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
|
in: string
|
||||||
inactiveBranches: string
|
inactiveBranches: string
|
||||||
isRequired: string
|
isRequired: string
|
||||||
|
@ -589,6 +589,7 @@ spaceMemberships:
|
|||||||
memberUpdated: Member updated successfully.
|
memberUpdated: Member updated successfully.
|
||||||
memberAdded: Member added successfully.
|
memberAdded: Member added successfully.
|
||||||
failedToCreateSpace: Failed to create Space. Please try again.
|
failedToCreateSpace: Failed to create Space. Please try again.
|
||||||
|
failedToImportSpace: Failed to import Space. Please try again.
|
||||||
failedToCreatePipeline: Failed to create Pipeline. Please try again.
|
failedToCreatePipeline: Failed to create Pipeline. Please try again.
|
||||||
failedToSavePipeline: Failed to save Pipeline. Please try again.
|
failedToSavePipeline: Failed to save Pipeline. Please try again.
|
||||||
enterName: Enter the name
|
enterName: Enter the name
|
||||||
@ -692,3 +693,40 @@ plugins:
|
|||||||
title: Plugins
|
title: Plugins
|
||||||
addAPlugin: Add a {{category}} plugin
|
addAPlugin: Add a {{category}} plugin
|
||||||
stepLabel: step
|
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;
|
height: 100vh;
|
||||||
padding-top: 26% !important;
|
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 */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
|
export declare const bigButton: string
|
||||||
|
export declare const buttonContainer: string
|
||||||
export declare const container: string
|
export declare const container: string
|
||||||
export declare const main: string
|
export declare const main: string
|
||||||
export declare const spaceContainer: string
|
export declare const spaceContainer: string
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
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 { FontVariation } from '@harnessio/design-system'
|
||||||
import { noop } from 'lodash-es'
|
import { noop } from 'lodash-es'
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
@ -21,17 +21,20 @@ export default function Home() {
|
|||||||
|
|
||||||
const NewSpaceButton = (
|
const NewSpaceButton = (
|
||||||
<NewSpaceModalButton
|
<NewSpaceModalButton
|
||||||
|
size={ButtonSize.LARGE}
|
||||||
|
className={css.bigButton}
|
||||||
space={space}
|
space={space}
|
||||||
modalTitle={getString('createASpace')}
|
modalTitle={getString('createASpace')}
|
||||||
text={getString('newSpace')}
|
text={getString('newSpace')}
|
||||||
variation={ButtonVariation.PRIMARY}
|
variation={ButtonVariation.PRIMARY}
|
||||||
icon="plus"
|
icon="plus"
|
||||||
width={173}
|
|
||||||
height={48}
|
|
||||||
onRefetch={noop}
|
onRefetch={noop}
|
||||||
handleNavigation={spaceName => {
|
handleNavigation={spaceName => {
|
||||||
history.push(routes.toCODERepositories({ space: spaceName }))
|
history.push(routes.toCODERepositories({ space: spaceName }))
|
||||||
}}
|
}}
|
||||||
|
onSubmit={data => {
|
||||||
|
history.push(routes.toCODERepositories({ space: data.path as string }))
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
@ -51,7 +54,7 @@ export default function Home() {
|
|||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Text font={{ variation: FontVariation.BODY1 }}>{getString('homepage.firstStep')} </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}
|
{NewSpaceButton}
|
||||||
</Container>
|
</Container>
|
||||||
</Layout.Vertical>
|
</Layout.Vertical>
|
||||||
|
@ -39,13 +39,26 @@
|
|||||||
|
|
||||||
.row {
|
.row {
|
||||||
height: 80px;
|
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;
|
overflow: hidden;
|
||||||
|
|
||||||
&.noDesc > div {
|
&.noDesc > div {
|
||||||
height: 44px;
|
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 {
|
.nameContainer {
|
||||||
@ -102,3 +115,7 @@
|
|||||||
padding-top: var(--spacing-xsmall) !important;
|
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 nameContainer: string
|
||||||
export declare const noDesc: string
|
export declare const noDesc: string
|
||||||
export declare const pinned: string
|
export declare const pinned: string
|
||||||
|
export declare const progressBar: string
|
||||||
export declare const repoName: string
|
export declare const repoName: string
|
||||||
export declare const repoScope: string
|
export declare const repoScope: string
|
||||||
export declare const row: string
|
export declare const row: string
|
||||||
|
export declare const rowDisable: string
|
||||||
export declare const table: string
|
export declare const table: string
|
||||||
export declare const withError: string
|
export declare const withError: string
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
TableV2 as Table,
|
TableV2 as Table,
|
||||||
Text
|
Text
|
||||||
} from '@harnessio/uicore'
|
} from '@harnessio/uicore'
|
||||||
|
import { ProgressBar, Intent } from '@blueprintjs/core'
|
||||||
import { Icon } from '@harnessio/icons'
|
import { Icon } from '@harnessio/icons'
|
||||||
import { Color } from '@harnessio/design-system'
|
import { Color } from '@harnessio/design-system'
|
||||||
import type { CellProps, Column } from 'react-table'
|
import type { CellProps, Column } from 'react-table'
|
||||||
@ -31,6 +32,10 @@ import { NoResultCard } from 'components/NoResultCard/NoResultCard'
|
|||||||
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
import noRepoImage from './no-repo.svg'
|
import noRepoImage from './no-repo.svg'
|
||||||
import css from './RepositoriesListing.module.scss'
|
import css from './RepositoriesListing.module.scss'
|
||||||
|
import useSpaceSSE from 'hooks/useSpaceSSE'
|
||||||
|
interface TypesRepoExtended extends TypesRepository {
|
||||||
|
importing?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export default function RepositoriesListing() {
|
export default function RepositoriesListing() {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
@ -56,6 +61,18 @@ export default function RepositoriesListing() {
|
|||||||
queryParams: { page, limit: LIST_FETCHING_LIMIT, query: searchTerm },
|
queryParams: { page, limit: LIST_FETCHING_LIMIT, query: searchTerm },
|
||||||
debounce: 500
|
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(() => {
|
useEffect(() => {
|
||||||
setSearchTerm(undefined)
|
setSearchTerm(undefined)
|
||||||
@ -64,12 +81,13 @@ export default function RepositoriesListing() {
|
|||||||
}
|
}
|
||||||
}, [space, setPage]) // eslint-disable-line react-hooks/exhaustive-deps
|
}, [space, setPage]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const columns: Column<TypesRepository>[] = useMemo(
|
const columns: Column<TypesRepoExtended>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: getString('repos.name'),
|
Header: getString('repos.name'),
|
||||||
width: 'calc(100% - 180px)',
|
width: 'calc(100% - 180px)',
|
||||||
Cell: ({ row }: CellProps<TypesRepository>) => {
|
|
||||||
|
Cell: ({ row }: CellProps<TypesRepoExtended>) => {
|
||||||
const record = row.original
|
const record = row.original
|
||||||
return (
|
return (
|
||||||
<Container className={css.nameContainer}>
|
<Container className={css.nameContainer}>
|
||||||
@ -78,10 +96,16 @@ export default function RepositoriesListing() {
|
|||||||
<Text className={css.repoName} width={nameTextWidth} lineClamp={2}>
|
<Text className={css.repoName} width={nameTextWidth} lineClamp={2}>
|
||||||
<Keywords value={searchTerm}>{record.uid}</Keywords>
|
<Keywords value={searchTerm}>{record.uid}</Keywords>
|
||||||
</Text>
|
</Text>
|
||||||
{record.description && (
|
{record.importing ? (
|
||||||
<Text className={css.desc} width={nameTextWidth} lineClamp={1}>
|
<Text className={css.desc} width={nameTextWidth} lineClamp={1}>
|
||||||
{record.description}
|
{getString('importProgress')}
|
||||||
</Text>
|
</Text>
|
||||||
|
) : (
|
||||||
|
record.description && (
|
||||||
|
<Text className={css.desc} width={nameTextWidth} lineClamp={1}>
|
||||||
|
{record.description}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</Layout.Vertical>
|
</Layout.Vertical>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
@ -92,8 +116,12 @@ export default function RepositoriesListing() {
|
|||||||
{
|
{
|
||||||
Header: getString('repos.updated'),
|
Header: getString('repos.updated'),
|
||||||
width: '180px',
|
width: '180px',
|
||||||
Cell: ({ row }: CellProps<TypesRepository>) => {
|
Cell: ({ row }: CellProps<TypesRepoExtended>) => {
|
||||||
return (
|
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' }}>
|
<Layout.Horizontal style={{ alignItems: 'center' }}>
|
||||||
<Text color={Color.BLACK} lineClamp={1} rightIconProps={{ size: 10 }} width={120}>
|
<Text color={Color.BLACK} lineClamp={1} rightIconProps={{ size: 10 }} width={120}>
|
||||||
{formatDate(row.original.updated as number)}
|
{formatDate(row.original.updated as number)}
|
||||||
@ -107,6 +135,7 @@ export default function RepositoriesListing() {
|
|||||||
],
|
],
|
||||||
[nameTextWidth, getString, searchTerm]
|
[nameTextWidth, getString, searchTerm]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onResize = useCallback(() => {
|
const onResize = useCallback(() => {
|
||||||
if (rowContainerRef.current) {
|
if (rowContainerRef.current) {
|
||||||
setNameTextWidth((rowContainerRef.current.closest('div[role="cell"]') as HTMLDivElement)?.offsetWidth - 100)
|
setNameTextWidth((rowContainerRef.current.closest('div[role="cell"]') as HTMLDivElement)?.offsetWidth - 100)
|
||||||
@ -118,8 +147,13 @@ export default function RepositoriesListing() {
|
|||||||
modalTitle={getString('createARepo')}
|
modalTitle={getString('createARepo')}
|
||||||
text={getString('newRepo')}
|
text={getString('newRepo')}
|
||||||
variation={ButtonVariation.PRIMARY}
|
variation={ButtonVariation.PRIMARY}
|
||||||
icon="plus"
|
onSubmit={repoInfo => {
|
||||||
onSubmit={repoInfo => history.push(routes.toCODERepository({ repoPath: repoInfo.path as string }))}
|
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' }}>
|
<Container margin={{ top: 'medium' }}>
|
||||||
{!!repositories?.length && (
|
{!!repositories?.length && (
|
||||||
<Table<TypesRepository>
|
<Table<TypesRepoExtended>
|
||||||
className={css.table}
|
className={css.table}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={repositories || []}
|
data={repositories || []}
|
||||||
onRowClick={repoInfo => history.push(routes.toCODERepository({ repoPath: repoInfo.path as string }))}
|
onRowClick={repoInfo => {
|
||||||
getRowClassName={row => cx(css.row, !row.original.description && css.noDesc)}
|
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[]
|
commits: TypesCommit[]
|
||||||
pullRequestMetadata: TypesPullReq
|
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 {
|
export enum GitContentType {
|
||||||
FILE = 'file',
|
FILE = 'file',
|
||||||
@ -101,6 +142,11 @@ export const CodeIcon = {
|
|||||||
ChecksSuccess: 'success-tick' as IconName
|
ChecksSuccess: 'success-tick' as IconName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Organization {
|
||||||
|
GITHUB = 'Github',
|
||||||
|
GITLAB = 'Gitlab'
|
||||||
|
}
|
||||||
|
|
||||||
export const REFS_TAGS_PREFIX = 'refs/tags/'
|
export const REFS_TAGS_PREFIX = 'refs/tags/'
|
||||||
|
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
@ -168,3 +214,17 @@ export const decodeGitContent = (content = '') => {
|
|||||||
}
|
}
|
||||||
return ''
|
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