}); | }); | ||||
} | } | ||||
export function importBitbucketServerProject( | |||||
almSetting: string, | |||||
projectKey: string, | |||||
repositorySlug: string | |||||
): Promise<{ project: ProjectBase }> { | |||||
return postJSON('/api/alm_integrations/import_bitbucketserver_project', { | |||||
almSetting, | |||||
projectKey, | |||||
repositorySlug, | |||||
}).catch(throwGlobalError); | |||||
export function setupBitbucketServerProjectCreation(data: { | |||||
almSetting: string; | |||||
projectKey: string; | |||||
repositorySlug: string; | |||||
}) { | |||||
return (newCodeDefinitionType?: string, newCodeDefinitionValue?: string) => | |||||
importBitbucketServerProject({ ...data, newCodeDefinitionType, newCodeDefinitionValue }); | |||||
} | |||||
export function importBitbucketServerProject(data: { | |||||
almSetting: string; | |||||
projectKey: string; | |||||
repositorySlug: string; | |||||
newCodeDefinitionType?: string; | |||||
newCodeDefinitionValue?: string; | |||||
}): Promise<{ project: ProjectBase }> { | |||||
return postJSON('/api/alm_integrations/import_bitbucketserver_project', data).catch( | |||||
throwGlobalError | |||||
); | |||||
} | } | ||||
export function searchForBitbucketServerRepositories( | export function searchForBitbucketServerRepositories( |
searchForBitbucketServerRepositories, | searchForBitbucketServerRepositories, | ||||
setAlmPersonalAccessToken, | setAlmPersonalAccessToken, | ||||
setupAzureProjectCreation, | setupAzureProjectCreation, | ||||
setupBitbucketServerProjectCreation, | |||||
} from '../alm-integrations'; | } from '../alm-integrations'; | ||||
export default class AlmIntegrationsServiceMock { | export default class AlmIntegrationsServiceMock { | ||||
.mocked(getBitbucketServerRepositories) | .mocked(getBitbucketServerRepositories) | ||||
.mockImplementation(this.getBitbucketServerRepositories); | .mockImplementation(this.getBitbucketServerRepositories); | ||||
jest.mocked(importBitbucketServerProject).mockImplementation(this.importBitbucketServerProject); | jest.mocked(importBitbucketServerProject).mockImplementation(this.importBitbucketServerProject); | ||||
jest | |||||
.mocked(setupBitbucketServerProjectCreation) | |||||
.mockReturnValue(() => this.importBitbucketServerProject()); | |||||
jest | jest | ||||
.mocked(searchForBitbucketServerRepositories) | .mocked(searchForBitbucketServerRepositories) | ||||
.mockImplementation(this.searchForBitbucketServerRepositories); | .mockImplementation(this.searchForBitbucketServerRepositories); |
BitbucketProjectRepositories, | BitbucketProjectRepositories, | ||||
BitbucketRepository, | BitbucketRepository, | ||||
} from '../../../../types/alm-integration'; | } from '../../../../types/alm-integration'; | ||||
import InstanceNewCodeDefinitionComplianceWarning from '../components/InstanceNewCodeDefinitionComplianceWarning'; | |||||
import { CreateProjectModes } from '../types'; | import { CreateProjectModes } from '../types'; | ||||
import BitbucketRepositories from './BitbucketRepositories'; | import BitbucketRepositories from './BitbucketRepositories'; | ||||
import BitbucketSearchResults from './BitbucketSearchResults'; | import BitbucketSearchResults from './BitbucketSearchResults'; | ||||
export interface BitbucketImportRepositoryFormProps { | export interface BitbucketImportRepositoryFormProps { | ||||
disableRepositories: boolean; | |||||
onSearch: (query: string) => void; | onSearch: (query: string) => void; | ||||
onSelectRepository: (repo: BitbucketRepository) => void; | onSelectRepository: (repo: BitbucketRepository) => void; | ||||
projects?: BitbucketProject[]; | projects?: BitbucketProject[]; | ||||
export default function BitbucketImportRepositoryForm(props: BitbucketImportRepositoryFormProps) { | export default function BitbucketImportRepositoryForm(props: BitbucketImportRepositoryFormProps) { | ||||
const { | const { | ||||
disableRepositories, | |||||
projects = [], | projects = [], | ||||
projectRepositories = {}, | projectRepositories = {}, | ||||
searchResults, | searchResults, | ||||
return ( | return ( | ||||
<div className="create-project-import-bbs"> | <div className="create-project-import-bbs"> | ||||
<InstanceNewCodeDefinitionComplianceWarning /> | |||||
<SearchBox | <SearchBox | ||||
onChange={props.onSearch} | onChange={props.onSearch} | ||||
placeholder={translate('onboarding.create_project.search_repositories_by_name')} | placeholder={translate('onboarding.create_project.search_repositories_by_name')} | ||||
{searching || searchResults ? ( | {searching || searchResults ? ( | ||||
<BitbucketSearchResults | <BitbucketSearchResults | ||||
disableRepositories={disableRepositories} | |||||
onSelectRepository={props.onSelectRepository} | onSelectRepository={props.onSelectRepository} | ||||
projects={projects} | projects={projects} | ||||
searchResults={searchResults} | searchResults={searchResults} | ||||
/> | /> | ||||
) : ( | ) : ( | ||||
<BitbucketRepositories | <BitbucketRepositories | ||||
disableRepositories={disableRepositories} | |||||
onSelectRepository={props.onSelectRepository} | onSelectRepository={props.onSelectRepository} | ||||
projectRepositories={projectRepositories} | projectRepositories={projectRepositories} | ||||
projects={projects} | projects={projects} |
import { CreateProjectModes } from '../types'; | import { CreateProjectModes } from '../types'; | ||||
export interface BitbucketProjectAccordionProps { | export interface BitbucketProjectAccordionProps { | ||||
disableRepositories: boolean; | |||||
onClick?: () => void; | onClick?: () => void; | ||||
onSelectRepository: (repo: BitbucketRepository) => void; | onSelectRepository: (repo: BitbucketRepository) => void; | ||||
open: boolean; | open: boolean; | ||||
} | } | ||||
export default function BitbucketProjectAccordion(props: BitbucketProjectAccordionProps) { | export default function BitbucketProjectAccordion(props: BitbucketProjectAccordionProps) { | ||||
const { | |||||
disableRepositories, | |||||
open, | |||||
project, | |||||
repositories, | |||||
selectedRepository, | |||||
showingAllRepositories, | |||||
} = props; | |||||
const { open, project, repositories, selectedRepository, showingAllRepositories } = props; | |||||
const repositoryCount = repositories.length; | const repositoryCount = repositories.length; | ||||
) : ( | ) : ( | ||||
<Radio | <Radio | ||||
checked={selectedRepository?.id === repo.id} | checked={selectedRepository?.id === repo.id} | ||||
className={classNames( | |||||
'display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden', | |||||
{ | |||||
disabled: disableRepositories, | |||||
} | |||||
)} | |||||
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden" | |||||
key={repo.id} | key={repo.id} | ||||
onCheck={() => props.onSelectRepository(repo)} | onCheck={() => props.onSelectRepository(repo)} | ||||
value={String(repo.id)} | value={String(repo.id)} |
import { | import { | ||||
getBitbucketServerProjects, | getBitbucketServerProjects, | ||||
getBitbucketServerRepositories, | getBitbucketServerRepositories, | ||||
importBitbucketServerProject, | |||||
searchForBitbucketServerRepositories, | searchForBitbucketServerRepositories, | ||||
setupBitbucketServerProjectCreation, | |||||
} from '../../../../api/alm-integrations'; | } from '../../../../api/alm-integrations'; | ||||
import { Location, Router } from '../../../../components/hoc/withRouter'; | import { Location, Router } from '../../../../components/hoc/withRouter'; | ||||
import { | import { | ||||
} from '../../../../types/alm-integration'; | } from '../../../../types/alm-integration'; | ||||
import { AlmSettingsInstance } from '../../../../types/alm-settings'; | import { AlmSettingsInstance } from '../../../../types/alm-settings'; | ||||
import { DEFAULT_BBS_PAGE_SIZE } from '../constants'; | import { DEFAULT_BBS_PAGE_SIZE } from '../constants'; | ||||
import { CreateProjectApiCallback } from '../types'; | |||||
import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer'; | import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer'; | ||||
interface Props { | interface Props { | ||||
canAdmin: boolean; | canAdmin: boolean; | ||||
almInstances: AlmSettingsInstance[]; | almInstances: AlmSettingsInstance[]; | ||||
loadingBindings: boolean; | loadingBindings: boolean; | ||||
onProjectCreate: (projectKey: string) => void; | |||||
location: Location; | location: Location; | ||||
router: Router; | router: Router; | ||||
onProjectSetupDone: (createProject: CreateProjectApiCallback) => void; | |||||
} | } | ||||
interface State { | interface State { | ||||
selectedAlmInstance?: AlmSettingsInstance; | selectedAlmInstance?: AlmSettingsInstance; | ||||
importing: boolean; | |||||
loading: boolean; | loading: boolean; | ||||
projects?: BitbucketProject[]; | projects?: BitbucketProject[]; | ||||
projectRepositories?: BitbucketProjectRepositories; | projectRepositories?: BitbucketProjectRepositories; | ||||
constructor(props: Props) { | constructor(props: Props) { | ||||
super(props); | super(props); | ||||
this.state = { | this.state = { | ||||
// For now, we only handle a single instance. So we always use the first | |||||
// one from the list. | |||||
selectedAlmInstance: props.almInstances[0], | selectedAlmInstance: props.almInstances[0], | ||||
importing: false, | |||||
loading: false, | loading: false, | ||||
searching: false, | searching: false, | ||||
showPersonalAccessTokenForm: true, | showPersonalAccessTokenForm: true, | ||||
handleImportRepository = () => { | handleImportRepository = () => { | ||||
const { selectedAlmInstance, selectedRepository } = this.state; | const { selectedAlmInstance, selectedRepository } = this.state; | ||||
if (!selectedAlmInstance || !selectedRepository) { | |||||
return; | |||||
if (selectedAlmInstance && selectedRepository) { | |||||
this.props.onProjectSetupDone( | |||||
setupBitbucketServerProjectCreation({ | |||||
almSetting: selectedAlmInstance.key, | |||||
projectKey: selectedRepository.projectKey, | |||||
repositorySlug: selectedRepository.slug, | |||||
}) | |||||
); | |||||
} | } | ||||
this.setState({ importing: true }); | |||||
importBitbucketServerProject( | |||||
selectedAlmInstance.key, | |||||
selectedRepository.projectKey, | |||||
selectedRepository.slug | |||||
) | |||||
.then(({ project: { key } }) => { | |||||
if (this.mounted) { | |||||
this.setState({ importing: false }); | |||||
this.props.onProjectCreate(key); | |||||
} | |||||
}) | |||||
.catch(() => { | |||||
if (this.mounted) { | |||||
this.setState({ importing: false }); | |||||
} | |||||
}); | |||||
}; | }; | ||||
handleSearch = (query: string) => { | handleSearch = (query: string) => { | ||||
const { canAdmin, loadingBindings, location, almInstances } = this.props; | const { canAdmin, loadingBindings, location, almInstances } = this.props; | ||||
const { | const { | ||||
selectedAlmInstance, | selectedAlmInstance, | ||||
importing, | |||||
loading, | loading, | ||||
projectRepositories, | projectRepositories, | ||||
projects, | projects, | ||||
selectedAlmInstance={selectedAlmInstance} | selectedAlmInstance={selectedAlmInstance} | ||||
almInstances={almInstances} | almInstances={almInstances} | ||||
canAdmin={canAdmin} | canAdmin={canAdmin} | ||||
importing={importing} | |||||
loading={loading || loadingBindings} | loading={loading || loadingBindings} | ||||
onImportRepository={this.handleImportRepository} | onImportRepository={this.handleImportRepository} | ||||
onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated} | onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated} |
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { Button } from '../../../../components/controls/buttons'; | import { Button } from '../../../../components/controls/buttons'; | ||||
import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; | |||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
import { getBaseUrl } from '../../../../helpers/system'; | import { getBaseUrl } from '../../../../helpers/system'; | ||||
import { | import { | ||||
selectedAlmInstance?: AlmSettingsInstance; | selectedAlmInstance?: AlmSettingsInstance; | ||||
almInstances: AlmSettingsInstance[]; | almInstances: AlmSettingsInstance[]; | ||||
canAdmin?: boolean; | canAdmin?: boolean; | ||||
importing: boolean; | |||||
loading: boolean; | loading: boolean; | ||||
onImportRepository: () => void; | onImportRepository: () => void; | ||||
onSearch: (query: string) => void; | onSearch: (query: string) => void; | ||||
almInstances, | almInstances, | ||||
selectedAlmInstance, | selectedAlmInstance, | ||||
canAdmin, | canAdmin, | ||||
importing, | |||||
loading, | loading, | ||||
projects, | projects, | ||||
projectRepositories, | projectRepositories, | ||||
additionalActions={ | additionalActions={ | ||||
!showPersonalAccessTokenForm && ( | !showPersonalAccessTokenForm && ( | ||||
<div className="display-flex-center pull-right"> | <div className="display-flex-center pull-right"> | ||||
<DeferredSpinner className="spacer-right" loading={importing} /> | |||||
<Button | <Button | ||||
className="button-large button-primary" | className="button-large button-primary" | ||||
disabled={!selectedRepository || importing} | |||||
disabled={!selectedRepository} | |||||
onClick={props.onImportRepository} | onClick={props.onImportRepository} | ||||
> | > | ||||
{translate('onboarding.create_project.import_selected_repo')} | {translate('onboarding.create_project.import_selected_repo')} | ||||
/> | /> | ||||
) : ( | ) : ( | ||||
<BitbucketImportRepositoryForm | <BitbucketImportRepositoryForm | ||||
disableRepositories={importing} | |||||
onSearch={props.onSearch} | onSearch={props.onSearch} | ||||
onSelectRepository={props.onSelectRepository} | onSelectRepository={props.onSelectRepository} | ||||
projectRepositories={projectRepositories} | projectRepositories={projectRepositories} |
import BitbucketProjectAccordion from './BitbucketProjectAccordion'; | import BitbucketProjectAccordion from './BitbucketProjectAccordion'; | ||||
export interface BitbucketRepositoriesProps { | export interface BitbucketRepositoriesProps { | ||||
disableRepositories: boolean; | |||||
onSelectRepository: (repo: BitbucketRepository) => void; | onSelectRepository: (repo: BitbucketRepository) => void; | ||||
projects: BitbucketProject[]; | projects: BitbucketProject[]; | ||||
projectRepositories: BitbucketProjectRepositories; | projectRepositories: BitbucketProjectRepositories; | ||||
} | } | ||||
export default function BitbucketRepositories(props: BitbucketRepositoriesProps) { | export default function BitbucketRepositories(props: BitbucketRepositoriesProps) { | ||||
const { disableRepositories, projects, projectRepositories, selectedRepository } = props; | |||||
const { projects, projectRepositories, selectedRepository } = props; | |||||
const [openProjectKeys, setOpenProjectKeys] = React.useState( | const [openProjectKeys, setOpenProjectKeys] = React.useState( | ||||
projects.length > 0 ? [projects[0].key] : [] | projects.length > 0 ? [projects[0].key] : [] | ||||
return ( | return ( | ||||
<BitbucketProjectAccordion | <BitbucketProjectAccordion | ||||
disableRepositories={disableRepositories} | |||||
key={project.key} | key={project.key} | ||||
onClick={() => handleClick(isOpen, project.key)} | onClick={() => handleClick(isOpen, project.key)} | ||||
onSelectRepository={props.onSelectRepository} | onSelectRepository={props.onSelectRepository} |
import BitbucketProjectAccordion from './BitbucketProjectAccordion'; | import BitbucketProjectAccordion from './BitbucketProjectAccordion'; | ||||
export interface BitbucketSearchResultsProps { | export interface BitbucketSearchResultsProps { | ||||
disableRepositories: boolean; | |||||
onSelectRepository: (repo: BitbucketRepository) => void; | onSelectRepository: (repo: BitbucketRepository) => void; | ||||
projects: BitbucketProject[]; | projects: BitbucketProject[]; | ||||
searching: boolean; | searching: boolean; | ||||
} | } | ||||
export default function BitbucketSearchResults(props: BitbucketSearchResultsProps) { | export default function BitbucketSearchResults(props: BitbucketSearchResultsProps) { | ||||
const { | |||||
disableRepositories, | |||||
projects, | |||||
searching, | |||||
searchResults = [], | |||||
selectedRepository, | |||||
} = props; | |||||
const { projects, searching, searchResults = [], selectedRepository } = props; | |||||
if (searchResults.length === 0 && !searching) { | if (searchResults.length === 0 && !searching) { | ||||
return ( | return ( | ||||
<DeferredSpinner loading={searching}> | <DeferredSpinner loading={searching}> | ||||
{filteredSearchResults.length > 0 && ( | {filteredSearchResults.length > 0 && ( | ||||
<BitbucketProjectAccordion | <BitbucketProjectAccordion | ||||
disableRepositories={disableRepositories} | |||||
onSelectRepository={props.onSelectRepository} | onSelectRepository={props.onSelectRepository} | ||||
open | open | ||||
repositories={filteredSearchResults} | repositories={filteredSearchResults} | ||||
return ( | return ( | ||||
<BitbucketProjectAccordion | <BitbucketProjectAccordion | ||||
disableRepositories={disableRepositories} | |||||
key={project.key} | key={project.key} | ||||
onSelectRepository={props.onSelectRepository} | onSelectRepository={props.onSelectRepository} | ||||
open | open |
almInstances={bitbucketSettings} | almInstances={bitbucketSettings} | ||||
loadingBindings={loading} | loadingBindings={loading} | ||||
location={location} | location={location} | ||||
onProjectCreate={this.handleProjectCreate} | |||||
router={router} | router={router} | ||||
onProjectSetupDone={this.handleProjectSetupDone} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
await user.click(radioButton); | await user.click(radioButton); | ||||
expect(importButton).toBeEnabled(); | expect(importButton).toBeEnabled(); | ||||
await user.click(importButton); | await user.click(importButton); | ||||
expect( | |||||
screen.getByRole('heading', { name: 'onboarding.create_project.new_code_definition.title' }) | |||||
).toBeInTheDocument(); | |||||
await user.click(screen.getByRole('radio', { name: 'new_code_definition.global_setting' })); | |||||
await user.click( | |||||
screen.getByRole('button', { | |||||
name: 'onboarding.create_project.new_code_definition.create_project', | |||||
}) | |||||
); | |||||
expect(await screen.findByText('/dashboard?id=key')).toBeInTheDocument(); | expect(await screen.findByText('/dashboard?id=key')).toBeInTheDocument(); | ||||
}); | }); | ||||