/* * 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 { Dialog, Intent, PopoverPosition, Classes } from '@blueprintjs/core' import * as yup from 'yup' import { Button, ButtonProps, Container, Layout, FlexExpander, Formik, FormikForm, Heading, useToaster, FormInput, ButtonVariation, SplitButton, Text, SplitButtonOption } from '@harnessio/uicore' import { Icon } from '@harnessio/icons' import { Color, FontVariation } from '@harnessio/design-system' import { useMutate } from 'restful-react' import { get } from 'lodash-es' import { useModalHook } from 'hooks/useModalHook' import { useStrings } from 'framework/strings' import { getErrorMessage, permissionProps, REGEX_VALID_REPO_NAME } from 'utils/Utils' import type { TypesSpace, OpenapiCreateSpaceRequest } from 'services/code' import { useAppContext } from 'AppContext' import { ImportSpaceFormData, SpaceCreationType, GitProviders, getProviderTypeMapping } from 'utils/GitUtils' import ImportSpaceForm from './ImportSpaceForm/ImportSpaceForm' import css from './NewSpaceModalButton.module.scss' enum RepoVisibility { PUBLIC = 'public', PRIVATE = 'private' } interface SpaceFormData { name: string description: string license: string defaultBranch: string gitignore: string addReadme: boolean isPublic: RepoVisibility } const formInitialValues: SpaceFormData = { name: '', description: '', license: '', defaultBranch: 'main', gitignore: '', addReadme: false, isPublic: RepoVisibility.PRIVATE } export interface NewSpaceModalButtonProps extends Omit { 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 } export const NewSpaceModalButton: React.FC = ({ space, modalTitle, submitButtonTitle, cancelButtonTitle, onRefetch, handleNavigation, onSubmit, fromSpace = false, ...props }) => { const ModalComponent: React.FC = () => { const { standalone } = useAppContext() const { getString } = useStrings() const { showError } = useToaster() const { mutate: createSpace, loading: submitLoading } = useMutate({ verb: 'POST', path: `/api/v1/spaces` }) const { mutate: importSpace, loading: submitImportLoading } = useMutate({ verb: 'POST', path: `/api/v1/spaces/import` }) const loading = submitLoading || submitImportLoading const handleSubmit = async (formData: SpaceFormData) => { try { const payload: OpenapiCreateSpaceRequestExtended = { description: get(formData, 'description', '').trim(), is_public: get(formData, 'isPublic') === RepoVisibility.PUBLIC, uid: get(formData, 'name', '').trim(), parent_id: standalone ? Number(space) : 0 // TODO: Backend needs to fix parentID: accept string or number } await createSpace(payload) hideModal() handleNavigation?.(formData.name.trim()) onRefetch() } catch (exception) { showError(getErrorMessage(exception), 0, getString('failedToCreateSpace')) } } const handleImportSubmit = 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(), uid: formData.name.trim(), provider, provider_space: formData.organization } const response = await importSpace(importPayload) hideModal() onSubmit(response) onRefetch() } catch (exception) { showError(getErrorMessage(exception), 0, getString('failedToImportSpace')) } } return ( {spaceOption.type === SpaceCreationType.IMPORT ? getString('importSpace.title') : modalTitle} {spaceOption.type === SpaceCreationType.IMPORT ? ( ) : ( ) } const { getString } = useStrings() const spaceCreateOptions: SpaceCreationOption[] = [ { type: SpaceCreationType.CREATE, title: getString('newSpace'), desc: getString('importSpace.createASpace') }, { type: SpaceCreationType.IMPORT, title: getString('importSpace.title'), desc: getString('importSpace.title') } ] const [spaceOption, setSpaceOption] = useState(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 ( {spaceCreateOptions[0].title} } variation={ButtonVariation.PRIMARY} popoverProps={{ interactionKind: 'click', usePortal: true, captureDismiss: true, popoverClassName: fromSpace ? css.popoverSpace : css.popoverSplit, position: PopoverPosition.BOTTOM_RIGHT }} icon={'plus'} {...permissionProps(permResult, standalone)} onClick={() => { setSpaceOption(spaceCreateOptions[0]) setTimeout(() => openModal(), 0) }}> { setSpaceOption(spaceCreateOptions[1]) setTimeout(() => openModal(), 0) }} text={{getString('importSpace.title')}} /> ) } interface SpaceCreationOption { type: SpaceCreationType title: string desc: string }