diff --git a/web/src/components/NewRepoModalButton/ImportReposForm/ImportReposForm.tsx b/web/src/components/NewRepoModalButton/ImportReposForm/ImportReposForm.tsx
new file mode 100644
index 000000000..abdd6d4b0
--- /dev/null
+++ b/web/src/components/NewRepoModalButton/ImportReposForm/ImportReposForm.tsx
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2023 Harness, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { 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 { type ImportSpaceFormData, GitProviders, getProviders, getOrgLabel, getOrgPlaceholder } from 'utils/GitUtils'
+import css from '../../NewSpaceModalButton/NewSpaceModalButton.module.scss'
+
+interface ImportReposProps {
+ handleSubmit: (data: ImportSpaceFormData) => void
+ loading: boolean
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ hideModal: any
+ spaceRef: string
+}
+
+const getHostPlaceHolder = (gitProvider: string) => {
+ switch (gitProvider) {
+ case GitProviders.GITHUB:
+ case GitProviders.GITHUB_ENTERPRISE:
+ return 'enterGithubPlaceholder'
+ case GitProviders.GITLAB:
+ case GitProviders.GITLAB_SELF_HOSTED:
+ return 'enterGitlabPlaceholder'
+ case GitProviders.BITBUCKET:
+ case GitProviders.BITBUCKET_SERVER:
+ return 'enterBitbucketPlaceholder'
+ default:
+ return 'enterAddress'
+ }
+}
+
+const ImportReposForm = (props: ImportReposProps) => {
+ const { handleSubmit, loading, hideModal, spaceRef } = props
+ const { getString } = useStrings()
+ const [auth, setAuth] = useState(false)
+ const [step, setStep] = useState(0)
+ const [buttonLoading, setButtonLoading] = useState(false)
+
+ const formInitialValues: ImportSpaceFormData = {
+ gitProvider: GitProviders.GITHUB,
+ username: '',
+ password: '',
+ name: spaceRef,
+ description: '',
+ organization: '',
+ host: ''
+ }
+
+ const validationSchemaStepOne = yup.object().shape({
+ gitProvider: yup.string().trim().required(getString('importSpace.providerRequired'))
+ })
+
+ const validationSchemaStepTwo = yup.object().shape({
+ organization: yup.string().trim().required(getString('importSpace.orgRequired')),
+ name: yup.string().trim().required(getString('importSpace.spaceNameRequired'))
+ })
+
+ return (
+
+ {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 () => {
+ await handleSubmit(formik.values)
+ setButtonLoading(false)
+ }
+
+ return (
+
+
+ {step === 0 ? (
+ <>
+
+
+
+ {getString('importRepos.content')}
+
+
+
+
+
+
+ {formik.errors.gitProvider ? (
+
+ {formik.errors.gitProvider}
+
+ ) : null}
+ {![GitProviders.GITHUB, GitProviders.GITLAB, GitProviders.BITBUCKET].includes(
+ values.gitProvider
+ ) && (
+
+ )}
+ {formik.errors.host ? (
+
+ {formik.errors.host}
+
+ ) : null}
+
+ {getString('importSpace.authorization')}
+
+
+
+
+ {formik.values.gitProvider === GitProviders.BITBUCKET && (
+
+ )}
+ {formik.errors.username ? (
+
+ {formik.errors.username}
+
+ ) : null}
+
+ {formik.errors.password ? (
+
+ {formik.errors.password}
+
+ ) : null}
+
+ >
+ ) : null}
+ {step === 1 ? (
+ <>
+
+
+ {getString('importSpace.details')}
+
+
+
+
+
+
+ {
+ const target = event.target as HTMLInputElement
+ formik.setFieldValue('organization', target.value)
+ if (target.value) {
+ formik.validateField('organization')
+ }
+ }}
+ />
+ {formik.errors.organization ? (
+
+ {formik.errors.organization}
+
+ ) : null}
+
+
+
+
+
+
+
+ {
+ setAuth(!auth)
+ }}
+ disabled
+ padding={{ right: 'small' }}
+ className={css.checkbox}
+ />
+
+ {
+ setAuth(!auth)
+ }}
+ />
+
+
+
+
+
+
+ {formik.errors.name ? (
+
+ {formik.errors.name}
+
+ ) : null}
+
+
+ >
+ ) : null}
+
+
+
+
+ {step === 1 ? (
+
+
+
+ )
+ }}
+
+ )
+}
+
+export default ImportReposForm
diff --git a/web/src/components/NewRepoModalButton/NewRepoModalButton.tsx b/web/src/components/NewRepoModalButton/NewRepoModalButton.tsx
index 4e908980d..4501f6e6c 100644
--- a/web/src/components/NewRepoModalButton/NewRepoModalButton.tsx
+++ b/web/src/components/NewRepoModalButton/NewRepoModalButton.tsx
@@ -59,15 +59,17 @@ import {
import {
GitProviders,
ImportFormData,
+ ImportSpaceFormData,
RepoCreationType,
RepoFormData,
RepoVisibility,
isGitBranchNameValid,
getProviderTypeMapping
} from 'utils/GitUtils'
-import type { TypesRepository, OpenapiCreateRepositoryRequest } from 'services/code'
+import type { TypesSpace, TypesRepository, OpenapiCreateRepositoryRequest } from 'services/code'
import { useAppContext } from 'AppContext'
import ImportForm from './ImportForm/ImportForm'
+import ImportReposForm from './ImportReposForm/ImportReposForm'
import Private from '../../icons/private.svg'
import css from './NewRepoModalButton.module.scss'
@@ -120,6 +122,10 @@ export const NewRepoModalButton: React.FC = ({
space_path: space
}
})
+ const { mutate: importMultipleRepositories, loading: submitImportLoading } = useMutate({
+ verb: 'POST',
+ path: `/api/v1/spaces/${space}/+/import`
+ })
const {
data: gitignores,
loading: gitIgnoreLoading,
@@ -130,7 +136,7 @@ export const NewRepoModalButton: React.FC = ({
loading: licenseLoading,
error: licenseError
} = useGet({ path: '/api/v1/resources/license' })
- const loading = submitLoading || gitIgnoreLoading || licenseLoading || importRepoLoading
+ const loading = submitLoading || gitIgnoreLoading || licenseLoading || importRepoLoading || submitImportLoading
useEffect(() => {
if (gitIgnoreError || licenseError) {
@@ -192,6 +198,36 @@ export const NewRepoModalButton: React.FC = ({
showError(getErrorMessage(_error), 0, getString('importRepo.failedToImportRepo'))
})
}
+
+ const handleMultiRepoImportSubmit = async (formData: ImportSpaceFormData) => {
+ const type = getProviderTypeMapping(formData.gitProvider)
+
+ const provider = {
+ type,
+ username: formData.username,
+ password: formData.password,
+ host: ''
+ }
+
+ if (![GitProviders.GITHUB, GitProviders.GITLAB, GitProviders.BITBUCKET].includes(formData.gitProvider)) {
+ provider.host = formData.host
+ }
+
+ try {
+ const importPayload = {
+ description: (formData.description || '').trim(),
+ parent_ref: space,
+ uid: formData.name.trim(),
+ provider,
+ provider_space: formData.organization
+ }
+ const response = await importMultipleRepositories(importPayload)
+ hideModal()
+ onSubmit(response)
+ } catch (exception) {
+ showError(getErrorMessage(exception), 0, getString('failedToImportSpace'))
+ }
+ }
return (