@@ -37,7 +37,7 @@ export function getAlmDefinitions(): Promise<AlmSettingsBindingDefinitions> { | |||
return getJSON('/api/alm_settings/list_definitions').catch(throwGlobalError); | |||
} | |||
export function getAlmSettings(project: string): Promise<AlmSettingsInstance[]> { | |||
export function getAlmSettings(project?: string): Promise<AlmSettingsInstance[]> { | |||
return getJSON('/api/alm_settings/list', { project }) | |||
.then(({ almSettings }) => almSettings) | |||
.catch(throwGlobalError); |
@@ -380,6 +380,11 @@ th.huge-spacer-right { | |||
align-items: flex-start; | |||
} | |||
.display-flex-wrap { | |||
display: flex !important; | |||
flex-wrap: wrap; | |||
} | |||
.display-inline-flex-baseline { | |||
display: inline-flex !important; | |||
align-items: baseline; | |||
@@ -390,6 +395,11 @@ th.huge-spacer-right { | |||
align-items: center; | |||
} | |||
.display-inline-flex-start { | |||
display: inline-flex !important; | |||
align-items: start; | |||
} | |||
.position-absolute { | |||
position: absolute !important; | |||
} |
@@ -0,0 +1,131 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as classNames from 'classnames'; | |||
import { uniq, without } from 'lodash'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import BoxedGroupAccordion from 'sonar-ui-common/components/controls/BoxedGroupAccordion'; | |||
import { ButtonLink } from 'sonar-ui-common/components/controls/buttons'; | |||
import Radio from 'sonar-ui-common/components/controls/Radio'; | |||
import CheckIcon from 'sonar-ui-common/components/icons/CheckIcon'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { colors } from '../../../app/theme'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration'; | |||
export interface BitbucketImportRepositoryFormProps { | |||
importing?: boolean; | |||
onSelectRepository: (repo: BitbucketRepository) => void; | |||
projects?: BitbucketProject[]; | |||
projectRepositories?: T.Dict<BitbucketRepository[]>; | |||
selectedRepository?: BitbucketRepository; | |||
} | |||
export default function BitbucketImportRepositoryForm(props: BitbucketImportRepositoryFormProps) { | |||
const { importing, projects = [], projectRepositories = {}, selectedRepository } = props; | |||
const [openProjectKeys, setOpenProjectKeys] = React.useState( | |||
projects.length > 0 ? [projects[0].key] : [] | |||
); | |||
if (projects.length === 0) { | |||
return ( | |||
<Alert variant="warning">{translate('onboarding.create_project.no_bbs_projects')}</Alert> | |||
); | |||
} | |||
const allAreExpanded = projects.length === openProjectKeys.length; | |||
return ( | |||
<div className="create-project-import-bbs"> | |||
<div className="overflow-hidden spacer-bottom"> | |||
<ButtonLink | |||
className="pull-right" | |||
onClick={() => setOpenProjectKeys(allAreExpanded ? [] : projects.map(p => p.key))}> | |||
{allAreExpanded ? translate('collapse_all') : translate('expand_all')} | |||
</ButtonLink> | |||
</div> | |||
{projects.map(project => { | |||
const isOpen = openProjectKeys.includes(project.key); | |||
const repositories = projectRepositories[project.key] || []; | |||
return ( | |||
<BoxedGroupAccordion | |||
className={classNames({ open: isOpen })} | |||
key={project.key} | |||
onClick={() => | |||
setOpenProjectKeys( | |||
isOpen | |||
? without(openProjectKeys, project.key) | |||
: uniq([...openProjectKeys, project.key]) | |||
) | |||
} | |||
open={isOpen} | |||
title={<h3>{project.name}</h3>}> | |||
{isOpen && ( | |||
<div className="display-flex-wrap"> | |||
{repositories.length === 0 && ( | |||
<Alert variant="warning"> | |||
{translate('onboarding.create_project.no_bbs_repos')} | |||
</Alert> | |||
)} | |||
{repositories.map(repo => | |||
repo.sqProjectKey ? ( | |||
<span | |||
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo" | |||
key={repo.id}> | |||
<CheckIcon className="spacer-right" fill={colors.green} size={14} /> | |||
<span> | |||
<div className="little-spacer-bottom"> | |||
<strong> | |||
<Link to={getProjectUrl(repo.sqProjectKey)}>{repo.name}</Link> | |||
</strong> | |||
</div> | |||
<em>{translate('onboarding.create_project.repository_imported')}</em> | |||
</span> | |||
</span> | |||
) : ( | |||
<Radio | |||
checked={selectedRepository?.id === repo.id} | |||
className={classNames( | |||
'display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden', | |||
{ | |||
disabled: importing, | |||
'text-muted': importing, | |||
'link-no-underline': importing | |||
} | |||
)} | |||
key={repo.id} | |||
onCheck={() => props.onSelectRepository(repo)} | |||
value={String(repo.id)}> | |||
<strong className="text-ellipsis">{repo.name}</strong> | |||
</Radio> | |||
) | |||
)} | |||
</div> | |||
)} | |||
</BoxedGroupAccordion> | |||
); | |||
})} | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,142 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; | |||
import { AlmSettingsInstance } from '../../../types/alm-settings'; | |||
export interface BitbucketPersonalAccessTokenFormProps { | |||
bitbucketSetting: AlmSettingsInstance; | |||
onPersonalAccessTokenCreate: (token: string) => void; | |||
submitting?: boolean; | |||
} | |||
export default function BitbucketPersonalAccessTokenForm( | |||
props: BitbucketPersonalAccessTokenFormProps | |||
) { | |||
const { | |||
bitbucketSetting: { url }, | |||
submitting = false | |||
} = props; | |||
const [personalAccessToken, setPersonalAccessToken] = React.useState(''); | |||
const isValid = personalAccessToken.length > 0; | |||
return ( | |||
<div className="display-flex-start"> | |||
<form | |||
onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => { | |||
e.preventDefault(); | |||
props.onPersonalAccessTokenCreate(personalAccessToken); | |||
}}> | |||
<h2 className="big">{translate('onboarding.create_project.grant_access_to_bbs.title')}</h2> | |||
<p className="big-spacer-top big-spacer-bottom"> | |||
{translate('onboarding.create_project.grant_access_to_bbs.help')} | |||
</p> | |||
<ValidationInput | |||
error={undefined} | |||
id="personal_access_token" | |||
isInvalid={false} | |||
isValid={isValid} | |||
label={translate('onboarding.create_project.enter_pat')} | |||
required={true}> | |||
<input | |||
autoFocus={true} | |||
className={classNames('input-super-large', { | |||
'is-valid': isValid | |||
})} | |||
id="personal_access_token" | |||
minLength={1} | |||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => | |||
setPersonalAccessToken(e.currentTarget.value) | |||
} | |||
type="text" | |||
value={personalAccessToken} | |||
/> | |||
</ValidationInput> | |||
<SubmitButton disabled={!isValid || submitting}>{translate('save')}</SubmitButton> | |||
<DeferredSpinner className="spacer-left" loading={submitting} /> | |||
</form> | |||
<Alert className="big-spacer-left big-spacer-top" display="block" variant="info"> | |||
<h3>{translate('onboarding.create_project.pat_help.title')}</h3> | |||
<p className="big-spacer-top big-spacer-bottom"> | |||
{translate('onboarding.create_project.pat_help.bbs_help_1')} | |||
</p> | |||
{url && ( | |||
<div className="text-middle"> | |||
<img | |||
alt="" // Should be ignored by screen readers | |||
className="spacer-right" | |||
height="16" | |||
src={`${getBaseUrl()}/images/alm/bitbucket.svg`} | |||
/> | |||
<a | |||
href={`${url.replace(/\/$/, '')}/plugins/servlet/access-tokens/add`} | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
{translate('onboarding.create_project.pat_help.link')} | |||
</a> | |||
</div> | |||
)} | |||
<p className="big-spacer-top big-spacer-bottom"> | |||
{translate('onboarding.create_project.pat_help.bbs_help_2')} | |||
</p> | |||
<ul> | |||
<li> | |||
<FormattedMessage | |||
defaultMessage={translate( | |||
'onboarding.create_project.pat_help.bbs_permission_projects' | |||
)} | |||
id="onboarding.create_project.pat_help.bbs_permission_projects" | |||
values={{ | |||
perm: ( | |||
<strong>{translate('onboarding.create_project.pat_help.read_permission')}</strong> | |||
) | |||
}} | |||
/> | |||
</li> | |||
<li> | |||
<FormattedMessage | |||
defaultMessage={translate('onboarding.create_project.pat_help.bbs_permission_repos')} | |||
id="onboarding.create_project.pat_help.bbs_permission_repos" | |||
values={{ | |||
perm: ( | |||
<strong>{translate('onboarding.create_project.pat_help.read_permission')}</strong> | |||
) | |||
}} | |||
/> | |||
</li> | |||
</ul> | |||
</Alert> | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,243 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { WithRouterProps } from 'react-router'; | |||
import { | |||
checkPersonalAccessTokenIsValid, | |||
getBitbucketServerProjects, | |||
getBitbucketServerRepositories, | |||
importBitbucketServerProject, | |||
setAlmPersonalAccessToken | |||
} from '../../../api/alm-integrations'; | |||
import { getAppState, Store } from '../../../store/rootReducer'; | |||
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration'; | |||
import { AlmSettingsInstance } from '../../../types/alm-settings'; | |||
import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer'; | |||
interface Props extends Pick<WithRouterProps, 'location'> { | |||
bitbucketSettings: AlmSettingsInstance[]; | |||
canAdmin?: boolean; | |||
loadingBindings: boolean; | |||
onProjectCreate: (projectKeys: string[]) => void; | |||
} | |||
interface State { | |||
bitbucketSetting?: AlmSettingsInstance; | |||
importing: boolean; | |||
loading: boolean; | |||
patIsValid?: boolean; | |||
projects?: BitbucketProject[]; | |||
projectRepositories?: T.Dict<BitbucketRepository[]>; | |||
selectedRepository?: BitbucketRepository; | |||
submittingToken?: boolean; | |||
} | |||
export class BitbucketProjectCreate extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
// For now, we only handle a single instance. So we always use the first | |||
// one from the list. | |||
bitbucketSetting: props.bitbucketSettings[0], | |||
importing: false, | |||
loading: false | |||
}; | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchInitialData(); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.bitbucketSettings.length === 0 && this.props.bitbucketSettings.length > 0) { | |||
this.setState({ bitbucketSetting: this.props.bitbucketSettings[0] }); | |||
this.fetchInitialData(); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
fetchInitialData = async () => { | |||
this.setState({ loading: true }); | |||
const patIsValid = await this.checkPersonalAccessToken().catch(() => false); | |||
let projects; | |||
if (patIsValid) { | |||
projects = await this.fetchBitbucketProjects().catch(() => undefined); | |||
} | |||
let projectRepositories; | |||
if (projects && projects.length > 0) { | |||
projectRepositories = await this.fetchBitbucketRepositories(projects).catch(() => undefined); | |||
} | |||
if (this.mounted) { | |||
this.setState({ | |||
patIsValid, | |||
projects, | |||
projectRepositories, | |||
loading: false | |||
}); | |||
} | |||
}; | |||
checkPersonalAccessToken = () => { | |||
const { bitbucketSetting } = this.state; | |||
if (!bitbucketSetting) { | |||
return Promise.resolve(false); | |||
} | |||
return checkPersonalAccessTokenIsValid(bitbucketSetting.key); | |||
}; | |||
fetchBitbucketProjects = (): Promise<BitbucketProject[] | undefined> => { | |||
const { bitbucketSetting } = this.state; | |||
if (!bitbucketSetting) { | |||
return Promise.resolve(undefined); | |||
} | |||
return getBitbucketServerProjects(bitbucketSetting.key).then(({ projects }) => projects); | |||
}; | |||
fetchBitbucketRepositories = ( | |||
projects: BitbucketProject[] | |||
): Promise<T.Dict<BitbucketRepository[]> | undefined> => { | |||
const { bitbucketSetting } = this.state; | |||
if (!bitbucketSetting) { | |||
return Promise.resolve(undefined); | |||
} | |||
return Promise.all( | |||
projects.map(p => { | |||
return getBitbucketServerRepositories(bitbucketSetting.key, p.name).then( | |||
({ repositories }) => ({ | |||
repositories, | |||
projectKey: p.key | |||
}) | |||
); | |||
}) | |||
).then(results => { | |||
return results.reduce((acc: T.Dict<BitbucketRepository[]>, { projectKey, repositories }) => { | |||
return { ...acc, [projectKey]: repositories }; | |||
}, {}); | |||
}); | |||
}; | |||
handlePersonalAccessTokenCreate = (token: string) => { | |||
const { bitbucketSetting } = this.state; | |||
if (!bitbucketSetting || token.length < 1) { | |||
return; | |||
} | |||
this.setState({ submittingToken: true }); | |||
setAlmPersonalAccessToken(bitbucketSetting.key, token) | |||
.then(() => { | |||
if (this.mounted) { | |||
this.setState({ submittingToken: false }); | |||
this.fetchInitialData(); | |||
} | |||
}) | |||
.catch(() => { | |||
if (this.mounted) { | |||
this.setState({ submittingToken: false }); | |||
} | |||
}); | |||
}; | |||
handleImportRepository = () => { | |||
const { bitbucketSetting, selectedRepository } = this.state; | |||
if (!bitbucketSetting || !selectedRepository) { | |||
return; | |||
} | |||
this.setState({ importing: true }); | |||
importBitbucketServerProject( | |||
bitbucketSetting.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 }); | |||
} | |||
}); | |||
}; | |||
handleSelectRepository = (selectedRepository: BitbucketRepository) => { | |||
this.setState({ selectedRepository }); | |||
}; | |||
render() { | |||
const { canAdmin, loadingBindings } = this.props; | |||
const { | |||
bitbucketSetting, | |||
importing, | |||
loading, | |||
patIsValid, | |||
projectRepositories, | |||
projects, | |||
selectedRepository, | |||
submittingToken | |||
} = this.state; | |||
return ( | |||
<BitbucketCreateProjectRenderer | |||
bitbucketSetting={bitbucketSetting} | |||
canAdmin={canAdmin} | |||
importing={importing} | |||
loading={loading || loadingBindings} | |||
onImportRepository={this.handleImportRepository} | |||
onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate} | |||
onProjectCreate={this.props.onProjectCreate} | |||
onSelectRepository={this.handleSelectRepository} | |||
projectRepositories={projectRepositories} | |||
projects={projects} | |||
selectedRepository={selectedRepository} | |||
showPersonalAccessTokenForm={!patIsValid} | |||
submittingToken={submittingToken} | |||
/> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: Store) => { | |||
const { canAdmin } = getAppState(state); | |||
return { canAdmin }; | |||
}; | |||
export default connect(mapStateToProps)(BitbucketProjectCreate); |
@@ -0,0 +1,139 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { Link } from 'react-router'; | |||
import { Button } from 'sonar-ui-common/components/controls/buttons'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; | |||
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration'; | |||
import { AlmSettingsInstance } from '../../../types/alm-settings'; | |||
import { PULL_REQUEST_DECORATION_BINDING_CATEGORY } from '../../settings/components/AdditionalCategoryKeys'; | |||
import BitbucketImportRepositoryForm from './BitbucketImportRepositoryForm'; | |||
import BitbucketPersonalAccessTokenForm from './BitbucketPersonalAccessTokenForm'; | |||
import CreateProjectPageHeader from './CreateProjectPageHeader'; | |||
export interface BitbucketProjectCreateRendererProps { | |||
bitbucketSetting?: AlmSettingsInstance; | |||
canAdmin?: boolean; | |||
importing: boolean; | |||
loading: boolean; | |||
onImportRepository: () => void; | |||
onSelectRepository: (repo: BitbucketRepository) => void; | |||
onPersonalAccessTokenCreate: (token: string) => void; | |||
onProjectCreate: (projectKeys: string[]) => void; | |||
projects?: BitbucketProject[]; | |||
projectRepositories?: T.Dict<BitbucketRepository[]>; | |||
selectedRepository?: BitbucketRepository; | |||
showPersonalAccessTokenForm?: boolean; | |||
submittingToken?: boolean; | |||
} | |||
export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCreateRendererProps) { | |||
const { | |||
bitbucketSetting, | |||
canAdmin, | |||
importing, | |||
loading, | |||
projects, | |||
projectRepositories, | |||
selectedRepository, | |||
showPersonalAccessTokenForm, | |||
submittingToken | |||
} = props; | |||
return ( | |||
<> | |||
<CreateProjectPageHeader | |||
additionalActions={ | |||
!showPersonalAccessTokenForm && ( | |||
<div className="display-flex-center pull-right"> | |||
<DeferredSpinner className="spacer-right" loading={importing} /> | |||
<Button | |||
className="button-large button-primary" | |||
disabled={!selectedRepository || importing} | |||
onClick={props.onImportRepository}> | |||
{translate('onboarding.create_project.import_selected_repo')} | |||
</Button> | |||
</div> | |||
) | |||
} | |||
showBreadcrumb={true} | |||
title={ | |||
<span className="text-middle"> | |||
<img | |||
alt="" // Should be ignored by screen readers | |||
className="spacer-right" | |||
height="24" | |||
src={`${getBaseUrl()}/images/alm/bitbucket.svg`} | |||
/> | |||
{translate('onboarding.create_project.from_bbs')} | |||
</span> | |||
} | |||
/> | |||
{loading && <i className="spinner" />} | |||
{!loading && !bitbucketSetting && ( | |||
<Alert variant="error"> | |||
{canAdmin ? ( | |||
<FormattedMessage | |||
defaultMessage={translate('onboarding.create_project.no_bbs_binding.admin')} | |||
id="onboarding.create_project.no_bbs_binding.admin" | |||
values={{ | |||
url: ( | |||
<Link | |||
to={{ | |||
pathname: '/admin/settings', | |||
query: { category: PULL_REQUEST_DECORATION_BINDING_CATEGORY } | |||
}}> | |||
{translate('settings.page')} | |||
</Link> | |||
) | |||
}} | |||
/> | |||
) : ( | |||
translate('onboarding.create_project.no_bbs_binding') | |||
)} | |||
</Alert> | |||
)} | |||
{!loading && | |||
bitbucketSetting && | |||
(showPersonalAccessTokenForm ? ( | |||
<BitbucketPersonalAccessTokenForm | |||
bitbucketSetting={bitbucketSetting} | |||
onPersonalAccessTokenCreate={props.onPersonalAccessTokenCreate} | |||
submitting={submittingToken} | |||
/> | |||
) : ( | |||
<BitbucketImportRepositoryForm | |||
importing={importing} | |||
onSelectRepository={props.onSelectRepository} | |||
projectRepositories={projectRepositories} | |||
projects={projects} | |||
selectedRepository={selectedRepository} | |||
/> | |||
))} | |||
</> | |||
); | |||
} |
@@ -0,0 +1,102 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; | |||
import { CreateProjectModes } from './types'; | |||
export interface CreateProjectModeSelectionProps { | |||
bbsBindingCount: number; | |||
loadingBindings: boolean; | |||
onSelectMode: (mode: CreateProjectModes) => void; | |||
} | |||
export default function CreateProjectModeSelection(props: CreateProjectModeSelectionProps) { | |||
const { bbsBindingCount, loadingBindings } = props; | |||
return ( | |||
<> | |||
<header className="huge-spacer-top big-spacer-bottom padded"> | |||
<h1 className="text-center huge big-spacer-bottom"> | |||
{translate('my_account.create_new.TRK')} | |||
</h1> | |||
<p className="text-center big">{translate('onboarding.create_project.select_method')}</p> | |||
</header> | |||
<div className="create-project-modes huge-spacer-top display-flex-space-around"> | |||
<button | |||
className="button button-huge display-flex-column create-project-mode-type-manual" | |||
onClick={() => props.onSelectMode(CreateProjectModes.Manual)} | |||
type="button"> | |||
<img | |||
alt="" // Should be ignored by screen readers | |||
height={80} | |||
src={`${getBaseUrl()}/images/sonarcloud/analysis/manual.svg`} | |||
width={80} | |||
/> | |||
<div className="medium big-spacer-top"> | |||
{translate('onboarding.create_project.select_method.manual')} | |||
</div> | |||
</button> | |||
<button | |||
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs" | |||
disabled={bbsBindingCount !== 1} | |||
onClick={() => props.onSelectMode(CreateProjectModes.BitbucketServer)} | |||
type="button"> | |||
<img | |||
alt="" // Should be ignored by screen readers | |||
height={80} | |||
src={`${getBaseUrl()}/images/alm/bitbucket.svg`} | |||
width={80} | |||
/> | |||
<div className="medium big-spacer-top"> | |||
{translate('onboarding.create_project.select_method.from_bbs')} | |||
</div> | |||
{loadingBindings && ( | |||
<span> | |||
{translate('onboarding.create_project.check_bbs_supported')} | |||
<i className="little-spacer-left spinner" /> | |||
</span> | |||
)} | |||
{!loadingBindings && bbsBindingCount !== 1 && ( | |||
<div className="text-muted small spacer-top" style={{ lineHeight: 1.5 }}> | |||
{translate('onboarding.create_project.bbs_not_configured')} | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay={ | |||
bbsBindingCount === 0 | |||
? translate('onboarding.create_project.zero_bbs_instances') | |||
: translateWithParameters( | |||
'onboarding.create_project.too_many_bbs_instances_X', | |||
bbsBindingCount | |||
) | |||
} | |||
/> | |||
</div> | |||
)} | |||
</button> | |||
</div> | |||
</> | |||
); | |||
} |
@@ -0,0 +1,48 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
export interface CreateProjectPageHeaderProps { | |||
additionalActions?: React.ReactNode; | |||
showBreadcrumb?: boolean; | |||
title: React.ReactNode; | |||
} | |||
export default function CreateProjectPageHeader(props: CreateProjectPageHeaderProps) { | |||
const { additionalActions, showBreadcrumb, title } = props; | |||
return ( | |||
<header className="huge-spacer-bottom bordered-bottom overflow-hidden"> | |||
<h1 className="pull-left huge big-spacer-bottom"> | |||
{showBreadcrumb && ( | |||
<> | |||
<Link to="/projects/create">{translate('my_account.create_new.TRK')}</Link> | |||
<span className="big-spacer-left big-spacer-right slash-separator" /> | |||
</> | |||
)} | |||
{title} | |||
</h1> | |||
{additionalActions} | |||
</header> | |||
); | |||
} |
@@ -22,50 +22,135 @@ import { Helmet } from 'react-helmet-async'; | |||
import { WithRouterProps } from 'react-router'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { addWhitePageClass, removeWhitePageClass } from 'sonar-ui-common/helpers/pages'; | |||
import { getAlmSettings } from '../../../api/almSettings'; | |||
import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn'; | |||
import { withAppState } from '../../../components/hoc/withAppState'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { AlmSettingsInstance, ALM_KEYS } from '../../../types/alm-settings'; | |||
import BitbucketProjectCreate from './BitbucketProjectCreate'; | |||
import CreateProjectModeSelection from './CreateProjectModeSelection'; | |||
import ManualProjectCreate from './ManualProjectCreate'; | |||
import './style.css'; | |||
import { CreateProjectModes } from './types'; | |||
interface Props { | |||
interface Props extends Pick<WithRouterProps, 'router' | 'location'> { | |||
appState: Pick<T.AppState, 'branchesEnabled'>; | |||
currentUser: T.LoggedInUser; | |||
} | |||
export class CreateProjectPageSonarQube extends React.PureComponent<Props & WithRouterProps> { | |||
interface State { | |||
bitbucketSettings: AlmSettingsInstance[]; | |||
loading: boolean; | |||
} | |||
export class CreateProjectPageSonarQube extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { bitbucketSettings: [], loading: false }; | |||
componentDidMount() { | |||
addWhitePageClass(); | |||
const { | |||
appState: { branchesEnabled }, | |||
location | |||
} = this.props; | |||
this.mounted = true; | |||
if (branchesEnabled) { | |||
this.fetchAlmBindings(); | |||
} | |||
if (location.query?.mode || !branchesEnabled) { | |||
addWhitePageClass(); | |||
} | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (this.props.location.query?.mode && !prevProps.location.query?.mode) { | |||
addWhitePageClass(); | |||
} else if (!this.props.location.query?.mode && prevProps.location.query?.mode) { | |||
removeWhitePageClass(); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
removeWhitePageClass(); | |||
} | |||
fetchAlmBindings = () => { | |||
this.setState({ loading: true }); | |||
getAlmSettings() | |||
.then(almSettings => { | |||
if (this.mounted) { | |||
this.setState({ | |||
bitbucketSettings: almSettings.filter(s => s.alm === ALM_KEYS.BITBUCKET), | |||
loading: false | |||
}); | |||
} | |||
}) | |||
.catch(() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}); | |||
}; | |||
handleProjectCreate = (projectKeys: string[]) => { | |||
if (projectKeys.length === 1) { | |||
this.props.router.push(getProjectUrl(projectKeys[0])); | |||
} | |||
}; | |||
handleModeSelect = (mode: CreateProjectModes) => { | |||
const { router, location } = this.props; | |||
router.push({ | |||
pathname: location.pathname, | |||
query: { mode } | |||
}); | |||
}; | |||
render() { | |||
const { currentUser } = this.props; | |||
const header = translate('my_account.create_new.TRK'); | |||
const { | |||
appState: { branchesEnabled }, | |||
currentUser, | |||
location | |||
} = this.props; | |||
const { bitbucketSettings, loading } = this.state; | |||
const mode: CreateProjectModes | undefined = location.query?.mode; | |||
const showManualForm = !branchesEnabled || mode === CreateProjectModes.Manual; | |||
const showBBSForm = branchesEnabled && mode === CreateProjectModes.BitbucketServer; | |||
return ( | |||
<> | |||
<Helmet title={header} titleTemplate="%s" /> | |||
<div className="page page-limited huge-spacer-top huge-spacer-bottom"> | |||
<header className="page-header bordered-bottom big-spacer-bottom"> | |||
<h1 className="page-title huge big-spacer-bottom"> | |||
<strong>{header}</strong> | |||
</h1> | |||
</header> | |||
<ManualProjectCreate | |||
currentUser={currentUser} | |||
onProjectCreate={this.handleProjectCreate} | |||
/> | |||
<Helmet title={translate('my_account.create_new.TRK')} titleTemplate="%s" /> | |||
<div className="page page-limited huge-spacer-bottom position-relative" id="create-project"> | |||
{!showBBSForm && !showManualForm && ( | |||
<CreateProjectModeSelection | |||
bbsBindingCount={bitbucketSettings.length} | |||
loadingBindings={loading} | |||
onSelectMode={this.handleModeSelect} | |||
/> | |||
)} | |||
{showManualForm && ( | |||
<ManualProjectCreate | |||
branchesEnabled={branchesEnabled} | |||
currentUser={currentUser} | |||
onProjectCreate={this.handleProjectCreate} | |||
/> | |||
)} | |||
{showBBSForm && ( | |||
<BitbucketProjectCreate | |||
bitbucketSettings={bitbucketSettings} | |||
loadingBindings={loading} | |||
location={location} | |||
onProjectCreate={this.handleProjectCreate} | |||
/> | |||
)} | |||
</div> | |||
</> | |||
); | |||
} | |||
} | |||
export default whenLoggedIn(CreateProjectPageSonarQube); | |||
export default whenLoggedIn(withAppState(CreateProjectPageSonarQube)); |
@@ -28,10 +28,12 @@ import { createProject, doesComponentExists } from '../../../api/components'; | |||
import VisibilitySelector from '../../../components/common/VisibilitySelector'; | |||
import { isSonarCloud } from '../../../helpers/system'; | |||
import UpgradeOrganizationBox from '../components/UpgradeOrganizationBox'; | |||
import CreateProjectPageHeader from './CreateProjectPageHeader'; | |||
import './ManualProjectCreate.css'; | |||
import OrganizationInput from './OrganizationInput'; | |||
interface Props { | |||
branchesEnabled?: boolean; | |||
currentUser: T.LoggedInUser; | |||
fetchMyOrganizations?: () => Promise<void>; | |||
onProjectCreate: (projectKeys: string[]) => void; | |||
@@ -248,6 +250,7 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat | |||
touched, | |||
validating | |||
} = this.state; | |||
const { branchesEnabled } = this.props; | |||
const projectKeyIsInvalid = touched && projectKeyError !== undefined; | |||
const projectKeyIsValid = touched && !validating && projectKeyError === undefined; | |||
const projectNameIsInvalid = touched && projectNameError !== undefined; | |||
@@ -255,97 +258,104 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat | |||
const canChoosePrivate = this.canChoosePrivate(selectedOrganization); | |||
return ( | |||
<div className="create-project"> | |||
<div className="flex-1 huge-spacer-right"> | |||
<form className="manual-project-create" onSubmit={this.handleFormSubmit}> | |||
{isSonarCloud() && this.props.userOrganizations && ( | |||
<OrganizationInput | |||
onChange={this.handleOrganizationSelect} | |||
organization={selectedOrganization ? selectedOrganization.key : ''} | |||
organizations={this.props.userOrganizations} | |||
/> | |||
)} | |||
<ValidationInput | |||
className="form-field" | |||
description={translate('onboarding.create_project.project_key.description')} | |||
error={projectKeyError} | |||
help={translate('onboarding.create_project.project_key.help')} | |||
id="project-key" | |||
isInvalid={projectKeyIsInvalid} | |||
isValid={projectKeyIsValid} | |||
label={translate('onboarding.create_project.project_key')} | |||
required={true}> | |||
<input | |||
autoFocus={true} | |||
className={classNames('input-super-large', { | |||
'is-invalid': projectKeyIsInvalid, | |||
'is-valid': projectKeyIsValid | |||
})} | |||
<> | |||
<CreateProjectPageHeader | |||
showBreadcrumb={branchesEnabled} | |||
title={translate('onboarding.create_project.setup_manually')} | |||
/> | |||
<div className="create-project"> | |||
<div className="flex-1 huge-spacer-right"> | |||
<form className="manual-project-create" onSubmit={this.handleFormSubmit}> | |||
{isSonarCloud() && this.props.userOrganizations && ( | |||
<OrganizationInput | |||
onChange={this.handleOrganizationSelect} | |||
organization={selectedOrganization ? selectedOrganization.key : ''} | |||
organizations={this.props.userOrganizations} | |||
/> | |||
)} | |||
<ValidationInput | |||
className="form-field" | |||
description={translate('onboarding.create_project.project_key.description')} | |||
error={projectKeyError} | |||
help={translate('onboarding.create_project.project_key.help')} | |||
id="project-key" | |||
maxLength={400} | |||
minLength={1} | |||
onChange={this.handleProjectKeyChange} | |||
type="text" | |||
value={projectKey} | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
className="form-field" | |||
description={translate('onboarding.create_project.display_name.description')} | |||
error={projectNameError} | |||
help={translate('onboarding.create_project.display_name.help')} | |||
id="project-name" | |||
isInvalid={projectNameIsInvalid} | |||
isValid={projectNameIsValid} | |||
label={translate('onboarding.create_project.display_name')} | |||
required={true}> | |||
<input | |||
className={classNames('input-super-large', { | |||
'is-invalid': projectNameIsInvalid, | |||
'is-valid': projectNameIsValid | |||
})} | |||
isInvalid={projectKeyIsInvalid} | |||
isValid={projectKeyIsValid} | |||
label={translate('onboarding.create_project.project_key')} | |||
required={true}> | |||
<input | |||
autoFocus={true} | |||
className={classNames('input-super-large', { | |||
'is-invalid': projectKeyIsInvalid, | |||
'is-valid': projectKeyIsValid | |||
})} | |||
id="project-key" | |||
maxLength={400} | |||
minLength={1} | |||
onChange={this.handleProjectKeyChange} | |||
type="text" | |||
value={projectKey} | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
className="form-field" | |||
description={translate('onboarding.create_project.display_name.description')} | |||
error={projectNameError} | |||
help={translate('onboarding.create_project.display_name.help')} | |||
id="project-name" | |||
maxLength={255} | |||
minLength={1} | |||
onChange={this.handleProjectNameChange} | |||
type="text" | |||
value={projectName} | |||
/> | |||
</ValidationInput> | |||
{isSonarCloud() && selectedOrganization && ( | |||
<div | |||
className={classNames('visibility-select-wrapper', { | |||
open: Boolean(this.state.selectedOrganization) | |||
})}> | |||
<VisibilitySelector | |||
canTurnToPrivate={canChoosePrivate} | |||
onChange={this.handleVisibilityChange} | |||
showDetails={true} | |||
visibility={canChoosePrivate ? this.state.selectedVisibility : 'public'} | |||
isInvalid={projectNameIsInvalid} | |||
isValid={projectNameIsValid} | |||
label={translate('onboarding.create_project.display_name')} | |||
required={true}> | |||
<input | |||
className={classNames('input-super-large', { | |||
'is-invalid': projectNameIsInvalid, | |||
'is-valid': projectNameIsValid | |||
})} | |||
id="project-name" | |||
maxLength={255} | |||
minLength={1} | |||
onChange={this.handleProjectNameChange} | |||
type="text" | |||
value={projectName} | |||
/> | |||
</div> | |||
)} | |||
<SubmitButton disabled={!this.canSubmit(this.state) || submitting}> | |||
{translate('set_up')} | |||
</SubmitButton> | |||
<DeferredSpinner className="spacer-left" loading={submitting} /> | |||
</form> | |||
</div> | |||
</ValidationInput> | |||
{isSonarCloud() && selectedOrganization && ( | |||
<div | |||
className={classNames('visibility-select-wrapper', { | |||
open: Boolean(this.state.selectedOrganization) | |||
})}> | |||
<VisibilitySelector | |||
canTurnToPrivate={canChoosePrivate} | |||
onChange={this.handleVisibilityChange} | |||
showDetails={true} | |||
visibility={canChoosePrivate ? this.state.selectedVisibility : 'public'} | |||
/> | |||
</div> | |||
)} | |||
{isSonarCloud() && selectedOrganization && ( | |||
<div className="create-project-side-sticky"> | |||
<UpgradeOrganizationBox | |||
className={classNames('animated', { open: !canChoosePrivate })} | |||
onOrganizationUpgrade={this.handleOrganizationUpgrade} | |||
organization={selectedOrganization} | |||
/> | |||
<SubmitButton disabled={!this.canSubmit(this.state) || submitting}> | |||
{translate('set_up')} | |||
</SubmitButton> | |||
<DeferredSpinner className="spacer-left" loading={submitting} /> | |||
</form> | |||
</div> | |||
)} | |||
</div> | |||
{isSonarCloud() && selectedOrganization && ( | |||
<div className="create-project-side-sticky"> | |||
<UpgradeOrganizationBox | |||
className={classNames('animated', { open: !canChoosePrivate })} | |||
onOrganizationUpgrade={this.handleOrganizationUpgrade} | |||
organization={selectedOrganization} | |||
/> | |||
</div> | |||
)} | |||
</div> | |||
</> | |||
); | |||
} | |||
} |
@@ -0,0 +1,84 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
/* eslint-disable sonarjs/no-duplicate-string */ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import BoxedGroupAccordion from 'sonar-ui-common/components/controls/BoxedGroupAccordion'; | |||
import Radio from 'sonar-ui-common/components/controls/Radio'; | |||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||
import { | |||
mockBitbucketProject, | |||
mockBitbucketRepository | |||
} from '../../../../helpers/mocks/alm-integrations'; | |||
import BitbucketImportRepositoryForm, { | |||
BitbucketImportRepositoryFormProps | |||
} from '../BitbucketImportRepositoryForm'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ importing: true })).toMatchSnapshot('importing'); | |||
expect(shallowRender({ projects: [] })).toMatchSnapshot('no projects'); | |||
expect(shallowRender({ projectRepositories: {} })).toMatchSnapshot('no repos'); | |||
expect(shallowRender({ selectedRepository: mockBitbucketRepository() })).toMatchSnapshot( | |||
'selected repo' | |||
); | |||
}); | |||
it('should correctly handle opening/closing accordions', () => { | |||
const wrapper = shallowRender(); | |||
click(wrapper.find(BoxedGroupAccordion).at(1)); | |||
expect(wrapper).toMatchSnapshot('2nd opened'); | |||
}); | |||
it('should correctly handle selecting repos', () => { | |||
const onSelectRepository = jest.fn(); | |||
const repo = mockBitbucketRepository(); | |||
const wrapper = shallowRender({ | |||
onSelectRepository, | |||
projectRepositories: { | |||
project: [repo] | |||
} | |||
}); | |||
wrapper | |||
.find(Radio) | |||
.at(0) | |||
.prop<Function>('onCheck')(); | |||
expect(onSelectRepository).toBeCalledWith(repo); | |||
}); | |||
function shallowRender(props: Partial<BitbucketImportRepositoryFormProps> = {}) { | |||
return shallow<BitbucketImportRepositoryFormProps>( | |||
<BitbucketImportRepositoryForm | |||
onSelectRepository={jest.fn()} | |||
projectRepositories={{ | |||
project: [ | |||
mockBitbucketRepository(), | |||
mockBitbucketRepository({ id: 2, slug: 'bar', name: 'Bar', sqProjectKey: 'bar' }) | |||
] | |||
}} | |||
projects={[ | |||
mockBitbucketProject(), | |||
mockBitbucketProject({ id: 2, key: 'project2', name: 'Project 2' }) | |||
]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import { change, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||
import { ALM_KEYS } from '../../../../types/alm-settings'; | |||
import BitbucketPersonalAccessTokenForm, { | |||
BitbucketPersonalAccessTokenFormProps | |||
} from '../BitbucketPersonalAccessTokenForm'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting'); | |||
}); | |||
it('should correctly handle form interactions', async () => { | |||
const onPersonalAccessTokenCreate = jest.fn(); | |||
const wrapper = shallowRender({ onPersonalAccessTokenCreate }); | |||
// Submit button disabled by default. | |||
expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); | |||
// Submit button enabled if there's a value. | |||
change(wrapper.find('input'), 'token'); | |||
expect(wrapper.find(SubmitButton).prop('disabled')).toBe(false); | |||
// Expect correct calls to be made when submitting. | |||
submit(wrapper.find('form')); | |||
await waitAndUpdate(wrapper); | |||
expect(onPersonalAccessTokenCreate).toBeCalled(); | |||
}); | |||
function shallowRender(props: Partial<BitbucketPersonalAccessTokenFormProps> = {}) { | |||
return shallow<BitbucketPersonalAccessTokenFormProps>( | |||
<BitbucketPersonalAccessTokenForm | |||
bitbucketSetting={mockAlmSettingsInstance({ | |||
alm: ALM_KEYS.BITBUCKET, | |||
url: 'http://www.example.com' | |||
})} | |||
onPersonalAccessTokenCreate={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,139 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { | |||
checkPersonalAccessTokenIsValid, | |||
getBitbucketServerProjects, | |||
getBitbucketServerRepositories, | |||
importBitbucketServerProject, | |||
setAlmPersonalAccessToken | |||
} from '../../../../api/alm-integrations'; | |||
import { mockBitbucketRepository } from '../../../../helpers/mocks/alm-integrations'; | |||
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||
import { mockLocation } from '../../../../helpers/testMocks'; | |||
import { ALM_KEYS } from '../../../../types/alm-settings'; | |||
import { BitbucketProjectCreate } from '../BitbucketProjectCreate'; | |||
jest.mock('../../../../api/alm-integrations', () => { | |||
const { mockBitbucketProject, mockBitbucketRepository } = jest.requireActual( | |||
'../../../../helpers/mocks/alm-integrations' | |||
); | |||
return { | |||
checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue(true), | |||
getBitbucketServerProjects: jest.fn().mockResolvedValue({ | |||
projects: [ | |||
mockBitbucketProject({ key: 'project1', name: 'Project 1' }), | |||
mockBitbucketProject({ id: 2, key: 'project2' }) | |||
] | |||
}), | |||
getBitbucketServerRepositories: jest.fn().mockResolvedValue({ | |||
repositories: [ | |||
mockBitbucketRepository(), | |||
mockBitbucketRepository({ id: 2, slug: 'project__repo2' }) | |||
] | |||
}), | |||
importBitbucketServerProject: jest.fn().mockResolvedValue({ project: { key: 'baz' } }), | |||
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null) | |||
}; | |||
}); | |||
beforeEach(jest.clearAllMocks); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should correctly fetch binding info on mount', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalledWith('foo'); | |||
}); | |||
it('should correctly handle a valid PAT', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(true); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalled(); | |||
expect(wrapper.state().patIsValid).toBe(true); | |||
}); | |||
it('should correctly handle an invalid PAT', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(false); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalled(); | |||
expect(wrapper.state().patIsValid).toBe(false); | |||
}); | |||
it('should correctly handle setting a new PAT', () => { | |||
const wrapper = shallowRender(); | |||
wrapper.instance().handlePersonalAccessTokenCreate('token'); | |||
expect(setAlmPersonalAccessToken).toBeCalledWith('foo', 'token'); | |||
}); | |||
it('should correctly fetch projects and repos', async () => { | |||
const wrapper = shallowRender(); | |||
// Opens first project on mount. | |||
await waitAndUpdate(wrapper); | |||
expect(getBitbucketServerProjects).toBeCalledWith('foo'); | |||
expect(wrapper.state().projects).toHaveLength(2); | |||
// Check repos got loaded. | |||
await waitAndUpdate(wrapper); | |||
expect(getBitbucketServerRepositories).toBeCalledWith('foo', 'Project 1'); | |||
expect(wrapper.state().projectRepositories).toEqual( | |||
expect.objectContaining({ | |||
project1: expect.arrayContaining([ | |||
expect.objectContaining({ id: 1 }), | |||
expect.objectContaining({ id: 2 }) | |||
]) | |||
}) | |||
); | |||
expect(wrapper.state().projectRepositories).toBeDefined(); | |||
}); | |||
it('should correctly import a repo', async () => { | |||
const onProjectCreate = jest.fn(); | |||
const repo = mockBitbucketRepository(); | |||
const wrapper = shallowRender({ onProjectCreate }); | |||
const instance = wrapper.instance(); | |||
instance.handleSelectRepository(repo); | |||
instance.handleImportRepository(); | |||
expect(importBitbucketServerProject).toBeCalledWith('foo', repo.projectKey, repo.slug); | |||
await waitAndUpdate(wrapper); | |||
expect(onProjectCreate).toBeCalledWith(['baz']); | |||
}); | |||
function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) { | |||
return shallow<BitbucketProjectCreate>( | |||
<BitbucketProjectCreate | |||
bitbucketSettings={[mockAlmSettingsInstance({ alm: ALM_KEYS.BITBUCKET, key: 'foo' })]} | |||
loadingBindings={false} | |||
location={mockLocation()} | |||
onProjectCreate={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { | |||
mockBitbucketProject, | |||
mockBitbucketRepository | |||
} from '../../../../helpers/mocks/alm-integrations'; | |||
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||
import { ALM_KEYS } from '../../../../types/alm-settings'; | |||
import BitbucketProjectCreateRenderer, { | |||
BitbucketProjectCreateRendererProps | |||
} from '../BitbucketProjectCreateRenderer'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ showPersonalAccessTokenForm: true })).toMatchSnapshot('pat form'); | |||
expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); | |||
expect(shallowRender({ importing: true })).toMatchSnapshot('importing'); | |||
expect(shallowRender({ selectedRepository: mockBitbucketRepository() })).toMatchSnapshot( | |||
'selected repo' | |||
); | |||
expect(shallowRender({ bitbucketSetting: undefined })).toMatchSnapshot( | |||
'invalid config, regular user' | |||
); | |||
expect(shallowRender({ bitbucketSetting: undefined, canAdmin: true })).toMatchSnapshot( | |||
'invalid config, admin user' | |||
); | |||
}); | |||
function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {}) { | |||
return shallow<BitbucketProjectCreateRendererProps>( | |||
<BitbucketProjectCreateRenderer | |||
bitbucketSetting={mockAlmSettingsInstance({ alm: ALM_KEYS.BITBUCKET })} | |||
importing={false} | |||
loading={false} | |||
onImportRepository={jest.fn()} | |||
onPersonalAccessTokenCreate={jest.fn()} | |||
onProjectCreate={jest.fn()} | |||
onSelectRepository={jest.fn()} | |||
projectRepositories={{ foo: [mockBitbucketRepository()] }} | |||
projects={[mockBitbucketProject({ key: 'foo' })]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,56 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||
import CreateProjectModeSelection, { | |||
CreateProjectModeSelectionProps | |||
} from '../CreateProjectModeSelection'; | |||
import { CreateProjectModes } from '../types'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ loadingBindings: true })).toMatchSnapshot('loading bbs instances'); | |||
expect(shallowRender({ bbsBindingCount: 0 })).toMatchSnapshot('no bbs instances'); | |||
expect(shallowRender({ bbsBindingCount: 2 })).toMatchSnapshot('too many bbs instances'); | |||
}); | |||
it('should correctly pass the selected mode up', () => { | |||
const onSelectMode = jest.fn(); | |||
const wrapper = shallowRender({ onSelectMode }); | |||
click(wrapper.find('button.create-project-mode-type-manual')); | |||
expect(onSelectMode).toBeCalledWith(CreateProjectModes.Manual); | |||
click(wrapper.find('button.create-project-mode-type-bbs')); | |||
expect(onSelectMode).toBeCalledWith(CreateProjectModes.BitbucketServer); | |||
}); | |||
function shallowRender(props: Partial<CreateProjectModeSelectionProps> = {}) { | |||
return shallow<CreateProjectModeSelectionProps>( | |||
<CreateProjectModeSelection | |||
bbsBindingCount={1} | |||
loadingBindings={false} | |||
onSelectMode={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,33 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import CreateProjectPageHeader, { CreateProjectPageHeaderProps } from '../CreateProjectPageHeader'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ showBreadcrumb: true })).toMatchSnapshot('with breadcrumb'); | |||
expect(shallowRender({ additionalActions: 'Bar' })).toMatchSnapshot('additional content'); | |||
}); | |||
function shallowRender(props: Partial<CreateProjectPageHeaderProps> = {}) { | |||
return shallow<CreateProjectPageHeaderProps>(<CreateProjectPageHeader title="Foo" {...props} />); | |||
} |
@@ -0,0 +1,85 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { addWhitePageClass } from 'sonar-ui-common/helpers/pages'; | |||
import { getAlmSettings } from '../../../../api/almSettings'; | |||
import { mockLocation, mockLoggedInUser, mockRouter } from '../../../../helpers/testMocks'; | |||
import { ALM_KEYS } from '../../../../types/alm-settings'; | |||
import { CreateProjectPageSonarQube } from '../CreateProjectPageSonarQube'; | |||
import { CreateProjectModes } from '../types'; | |||
jest.mock('../../../../api/almSettings', () => ({ | |||
getAlmSettings: jest.fn().mockResolvedValue([{ alm: ALM_KEYS.BITBUCKET, key: 'foo' }]) | |||
})); | |||
jest.mock('sonar-ui-common/helpers/pages', () => ({ | |||
addWhitePageClass: jest.fn(), | |||
removeWhitePageClass: jest.fn() | |||
})); | |||
beforeEach(jest.clearAllMocks); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(getAlmSettings).toBeCalled(); | |||
}); | |||
it('should render correctly if no branch support', () => { | |||
expect(shallowRender({ appState: { branchesEnabled: false } })).toMatchSnapshot(); | |||
expect(getAlmSettings).not.toBeCalled(); | |||
}); | |||
it('should render correctly if the manual method is selected', () => { | |||
const push = jest.fn(); | |||
const location = { query: { mode: CreateProjectModes.Manual } }; | |||
const wrapper = shallowRender({ router: mockRouter({ push }) }); | |||
wrapper.instance().handleModeSelect(CreateProjectModes.Manual); | |||
expect(push).toBeCalledWith(expect.objectContaining(location)); | |||
expect(wrapper.setProps({ location: mockLocation(location) })).toMatchSnapshot(); | |||
expect(addWhitePageClass).toBeCalled(); | |||
}); | |||
it('should render correctly if the BBS method is selected', () => { | |||
const push = jest.fn(); | |||
const location = { query: { mode: CreateProjectModes.BitbucketServer } }; | |||
const wrapper = shallowRender({ router: mockRouter({ push }) }); | |||
wrapper.instance().handleModeSelect(CreateProjectModes.BitbucketServer); | |||
expect(push).toBeCalledWith(expect.objectContaining(location)); | |||
expect(wrapper.setProps({ location: mockLocation(location) })).toMatchSnapshot(); | |||
expect(addWhitePageClass).toBeCalled(); | |||
}); | |||
function shallowRender(props: Partial<CreateProjectPageSonarQube['props']> = {}) { | |||
return shallow<CreateProjectPageSonarQube>( | |||
<CreateProjectPageSonarQube | |||
appState={{ branchesEnabled: true }} | |||
currentUser={mockLoggedInUser()} | |||
location={mockLocation()} | |||
router={mockRouter()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,440 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should correctly handle opening/closing accordions: 2nd opened 1`] = ` | |||
<div | |||
className="create-project-import-bbs" | |||
> | |||
<div | |||
className="overflow-hidden spacer-bottom" | |||
> | |||
<ButtonLink | |||
className="pull-right" | |||
onClick={[Function]} | |||
> | |||
collapse_all | |||
</ButtonLink> | |||
</div> | |||
<BoxedGroupAccordion | |||
className="open" | |||
key="project" | |||
onClick={[Function]} | |||
open={true} | |||
title={ | |||
<h3> | |||
Project | |||
</h3> | |||
} | |||
> | |||
<div | |||
className="display-flex-wrap" | |||
> | |||
<Radio | |||
checked={false} | |||
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden" | |||
key="1" | |||
onCheck={[Function]} | |||
value="1" | |||
> | |||
<strong | |||
className="text-ellipsis" | |||
> | |||
Repo | |||
</strong> | |||
</Radio> | |||
<span | |||
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo" | |||
key="2" | |||
> | |||
<CheckIcon | |||
className="spacer-right" | |||
fill="#00aa00" | |||
size={14} | |||
/> | |||
<span> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
<strong> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "bar", | |||
}, | |||
} | |||
} | |||
> | |||
Bar | |||
</Link> | |||
</strong> | |||
</div> | |||
<em> | |||
onboarding.create_project.repository_imported | |||
</em> | |||
</span> | |||
</span> | |||
</div> | |||
</BoxedGroupAccordion> | |||
<BoxedGroupAccordion | |||
className="open" | |||
key="project2" | |||
onClick={[Function]} | |||
open={true} | |||
title={ | |||
<h3> | |||
Project 2 | |||
</h3> | |||
} | |||
> | |||
<div | |||
className="display-flex-wrap" | |||
> | |||
<Alert | |||
variant="warning" | |||
> | |||
onboarding.create_project.no_bbs_repos | |||
</Alert> | |||
</div> | |||
</BoxedGroupAccordion> | |||
</div> | |||
`; | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="create-project-import-bbs" | |||
> | |||
<div | |||
className="overflow-hidden spacer-bottom" | |||
> | |||
<ButtonLink | |||
className="pull-right" | |||
onClick={[Function]} | |||
> | |||
expand_all | |||
</ButtonLink> | |||
</div> | |||
<BoxedGroupAccordion | |||
className="open" | |||
key="project" | |||
onClick={[Function]} | |||
open={true} | |||
title={ | |||
<h3> | |||
Project | |||
</h3> | |||
} | |||
> | |||
<div | |||
className="display-flex-wrap" | |||
> | |||
<Radio | |||
checked={false} | |||
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden" | |||
key="1" | |||
onCheck={[Function]} | |||
value="1" | |||
> | |||
<strong | |||
className="text-ellipsis" | |||
> | |||
Repo | |||
</strong> | |||
</Radio> | |||
<span | |||
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo" | |||
key="2" | |||
> | |||
<CheckIcon | |||
className="spacer-right" | |||
fill="#00aa00" | |||
size={14} | |||
/> | |||
<span> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
<strong> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "bar", | |||
}, | |||
} | |||
} | |||
> | |||
Bar | |||
</Link> | |||
</strong> | |||
</div> | |||
<em> | |||
onboarding.create_project.repository_imported | |||
</em> | |||
</span> | |||
</span> | |||
</div> | |||
</BoxedGroupAccordion> | |||
<BoxedGroupAccordion | |||
className="" | |||
key="project2" | |||
onClick={[Function]} | |||
open={false} | |||
title={ | |||
<h3> | |||
Project 2 | |||
</h3> | |||
} | |||
/> | |||
</div> | |||
`; | |||
exports[`should render correctly: importing 1`] = ` | |||
<div | |||
className="create-project-import-bbs" | |||
> | |||
<div | |||
className="overflow-hidden spacer-bottom" | |||
> | |||
<ButtonLink | |||
className="pull-right" | |||
onClick={[Function]} | |||
> | |||
expand_all | |||
</ButtonLink> | |||
</div> | |||
<BoxedGroupAccordion | |||
className="open" | |||
key="project" | |||
onClick={[Function]} | |||
open={true} | |||
title={ | |||
<h3> | |||
Project | |||
</h3> | |||
} | |||
> | |||
<div | |||
className="display-flex-wrap" | |||
> | |||
<Radio | |||
checked={false} | |||
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden disabled text-muted link-no-underline" | |||
key="1" | |||
onCheck={[Function]} | |||
value="1" | |||
> | |||
<strong | |||
className="text-ellipsis" | |||
> | |||
Repo | |||
</strong> | |||
</Radio> | |||
<span | |||
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo" | |||
key="2" | |||
> | |||
<CheckIcon | |||
className="spacer-right" | |||
fill="#00aa00" | |||
size={14} | |||
/> | |||
<span> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
<strong> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "bar", | |||
}, | |||
} | |||
} | |||
> | |||
Bar | |||
</Link> | |||
</strong> | |||
</div> | |||
<em> | |||
onboarding.create_project.repository_imported | |||
</em> | |||
</span> | |||
</span> | |||
</div> | |||
</BoxedGroupAccordion> | |||
<BoxedGroupAccordion | |||
className="" | |||
key="project2" | |||
onClick={[Function]} | |||
open={false} | |||
title={ | |||
<h3> | |||
Project 2 | |||
</h3> | |||
} | |||
/> | |||
</div> | |||
`; | |||
exports[`should render correctly: no projects 1`] = ` | |||
<Alert | |||
variant="warning" | |||
> | |||
onboarding.create_project.no_bbs_projects | |||
</Alert> | |||
`; | |||
exports[`should render correctly: no repos 1`] = ` | |||
<div | |||
className="create-project-import-bbs" | |||
> | |||
<div | |||
className="overflow-hidden spacer-bottom" | |||
> | |||
<ButtonLink | |||
className="pull-right" | |||
onClick={[Function]} | |||
> | |||
expand_all | |||
</ButtonLink> | |||
</div> | |||
<BoxedGroupAccordion | |||
className="open" | |||
key="project" | |||
onClick={[Function]} | |||
open={true} | |||
title={ | |||
<h3> | |||
Project | |||
</h3> | |||
} | |||
> | |||
<div | |||
className="display-flex-wrap" | |||
> | |||
<Alert | |||
variant="warning" | |||
> | |||
onboarding.create_project.no_bbs_repos | |||
</Alert> | |||
</div> | |||
</BoxedGroupAccordion> | |||
<BoxedGroupAccordion | |||
className="" | |||
key="project2" | |||
onClick={[Function]} | |||
open={false} | |||
title={ | |||
<h3> | |||
Project 2 | |||
</h3> | |||
} | |||
/> | |||
</div> | |||
`; | |||
exports[`should render correctly: selected repo 1`] = ` | |||
<div | |||
className="create-project-import-bbs" | |||
> | |||
<div | |||
className="overflow-hidden spacer-bottom" | |||
> | |||
<ButtonLink | |||
className="pull-right" | |||
onClick={[Function]} | |||
> | |||
expand_all | |||
</ButtonLink> | |||
</div> | |||
<BoxedGroupAccordion | |||
className="open" | |||
key="project" | |||
onClick={[Function]} | |||
open={true} | |||
title={ | |||
<h3> | |||
Project | |||
</h3> | |||
} | |||
> | |||
<div | |||
className="display-flex-wrap" | |||
> | |||
<Radio | |||
checked={true} | |||
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden" | |||
key="1" | |||
onCheck={[Function]} | |||
value="1" | |||
> | |||
<strong | |||
className="text-ellipsis" | |||
> | |||
Repo | |||
</strong> | |||
</Radio> | |||
<span | |||
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo" | |||
key="2" | |||
> | |||
<CheckIcon | |||
className="spacer-right" | |||
fill="#00aa00" | |||
size={14} | |||
/> | |||
<span> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
<strong> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "bar", | |||
}, | |||
} | |||
} | |||
> | |||
Bar | |||
</Link> | |||
</strong> | |||
</div> | |||
<em> | |||
onboarding.create_project.repository_imported | |||
</em> | |||
</span> | |||
</span> | |||
</div> | |||
</BoxedGroupAccordion> | |||
<BoxedGroupAccordion | |||
className="" | |||
key="project2" | |||
onClick={[Function]} | |||
open={false} | |||
title={ | |||
<h3> | |||
Project 2 | |||
</h3> | |||
} | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,225 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="display-flex-start" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<h2 | |||
className="big" | |||
> | |||
onboarding.create_project.grant_access_to_bbs.title | |||
</h2> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.grant_access_to_bbs.help | |||
</p> | |||
<ValidationInput | |||
id="personal_access_token" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.enter_pat" | |||
required={true} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-super-large" | |||
id="personal_access_token" | |||
minLength={1} | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
</form> | |||
<Alert | |||
className="big-spacer-left big-spacer-top" | |||
display="block" | |||
variant="info" | |||
> | |||
<h3> | |||
onboarding.create_project.pat_help.title | |||
</h3> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_help.bbs_help_1 | |||
</p> | |||
<div | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="16" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
<a | |||
href="http://www.example.com/plugins/servlet/access-tokens/add" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
onboarding.create_project.pat_help.link | |||
</a> | |||
</div> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_help.bbs_help_2 | |||
</p> | |||
<ul> | |||
<li> | |||
<FormattedMessage | |||
defaultMessage="onboarding.create_project.pat_help.bbs_permission_projects" | |||
id="onboarding.create_project.pat_help.bbs_permission_projects" | |||
values={ | |||
Object { | |||
"perm": <strong> | |||
onboarding.create_project.pat_help.read_permission | |||
</strong>, | |||
} | |||
} | |||
/> | |||
</li> | |||
<li> | |||
<FormattedMessage | |||
defaultMessage="onboarding.create_project.pat_help.bbs_permission_repos" | |||
id="onboarding.create_project.pat_help.bbs_permission_repos" | |||
values={ | |||
Object { | |||
"perm": <strong> | |||
onboarding.create_project.pat_help.read_permission | |||
</strong>, | |||
} | |||
} | |||
/> | |||
</li> | |||
</ul> | |||
</Alert> | |||
</div> | |||
`; | |||
exports[`should render correctly: submitting 1`] = ` | |||
<div | |||
className="display-flex-start" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<h2 | |||
className="big" | |||
> | |||
onboarding.create_project.grant_access_to_bbs.title | |||
</h2> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.grant_access_to_bbs.help | |||
</p> | |||
<ValidationInput | |||
id="personal_access_token" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.enter_pat" | |||
required={true} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-super-large" | |||
id="personal_access_token" | |||
minLength={1} | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={true} | |||
timeout={100} | |||
/> | |||
</form> | |||
<Alert | |||
className="big-spacer-left big-spacer-top" | |||
display="block" | |||
variant="info" | |||
> | |||
<h3> | |||
onboarding.create_project.pat_help.title | |||
</h3> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_help.bbs_help_1 | |||
</p> | |||
<div | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="16" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
<a | |||
href="http://www.example.com/plugins/servlet/access-tokens/add" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
onboarding.create_project.pat_help.link | |||
</a> | |||
</div> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_help.bbs_help_2 | |||
</p> | |||
<ul> | |||
<li> | |||
<FormattedMessage | |||
defaultMessage="onboarding.create_project.pat_help.bbs_permission_projects" | |||
id="onboarding.create_project.pat_help.bbs_permission_projects" | |||
values={ | |||
Object { | |||
"perm": <strong> | |||
onboarding.create_project.pat_help.read_permission | |||
</strong>, | |||
} | |||
} | |||
/> | |||
</li> | |||
<li> | |||
<FormattedMessage | |||
defaultMessage="onboarding.create_project.pat_help.bbs_permission_repos" | |||
id="onboarding.create_project.pat_help.bbs_permission_repos" | |||
values={ | |||
Object { | |||
"perm": <strong> | |||
onboarding.create_project.pat_help.read_permission | |||
</strong>, | |||
} | |||
} | |||
/> | |||
</li> | |||
</ul> | |||
</Alert> | |||
</div> | |||
`; |
@@ -0,0 +1,19 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<BitbucketProjectCreateRenderer | |||
bitbucketSetting={ | |||
Object { | |||
"alm": "bitbucket", | |||
"key": "foo", | |||
} | |||
} | |||
importing={false} | |||
loading={true} | |||
onImportRepository={[Function]} | |||
onPersonalAccessTokenCreate={[Function]} | |||
onProjectCreate={[MockFunction]} | |||
onSelectRepository={[Function]} | |||
showPersonalAccessTokenForm={true} | |||
/> | |||
`; |
@@ -0,0 +1,383 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
additionalActions={ | |||
<div | |||
className="display-flex-center pull-right" | |||
> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<Button | |||
className="button-large button-primary" | |||
disabled={true} | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.create_project.import_selected_repo | |||
</Button> | |||
</div> | |||
} | |||
showBreadcrumb={true} | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.from_bbs | |||
</span> | |||
} | |||
/> | |||
<BitbucketImportRepositoryForm | |||
importing={false} | |||
onSelectRepository={[MockFunction]} | |||
projectRepositories={ | |||
Object { | |||
"foo": Array [ | |||
Object { | |||
"id": 1, | |||
"name": "Repo", | |||
"projectKey": "project", | |||
"slug": "project__repo", | |||
}, | |||
], | |||
} | |||
} | |||
projects={ | |||
Array [ | |||
Object { | |||
"id": 1, | |||
"key": "foo", | |||
"name": "Project", | |||
}, | |||
] | |||
} | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: importing 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
additionalActions={ | |||
<div | |||
className="display-flex-center pull-right" | |||
> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={true} | |||
timeout={100} | |||
/> | |||
<Button | |||
className="button-large button-primary" | |||
disabled={true} | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.create_project.import_selected_repo | |||
</Button> | |||
</div> | |||
} | |||
showBreadcrumb={true} | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.from_bbs | |||
</span> | |||
} | |||
/> | |||
<BitbucketImportRepositoryForm | |||
importing={true} | |||
onSelectRepository={[MockFunction]} | |||
projectRepositories={ | |||
Object { | |||
"foo": Array [ | |||
Object { | |||
"id": 1, | |||
"name": "Repo", | |||
"projectKey": "project", | |||
"slug": "project__repo", | |||
}, | |||
], | |||
} | |||
} | |||
projects={ | |||
Array [ | |||
Object { | |||
"id": 1, | |||
"key": "foo", | |||
"name": "Project", | |||
}, | |||
] | |||
} | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: invalid config, admin user 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
additionalActions={ | |||
<div | |||
className="display-flex-center pull-right" | |||
> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<Button | |||
className="button-large button-primary" | |||
disabled={true} | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.create_project.import_selected_repo | |||
</Button> | |||
</div> | |||
} | |||
showBreadcrumb={true} | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.from_bbs | |||
</span> | |||
} | |||
/> | |||
<Alert | |||
variant="error" | |||
> | |||
<FormattedMessage | |||
defaultMessage="onboarding.create_project.no_bbs_binding.admin" | |||
id="onboarding.create_project.no_bbs_binding.admin" | |||
values={ | |||
Object { | |||
"url": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/admin/settings", | |||
"query": Object { | |||
"category": "pull_request_decoration", | |||
}, | |||
} | |||
} | |||
> | |||
settings.page | |||
</Link>, | |||
} | |||
} | |||
/> | |||
</Alert> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: invalid config, regular user 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
additionalActions={ | |||
<div | |||
className="display-flex-center pull-right" | |||
> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<Button | |||
className="button-large button-primary" | |||
disabled={true} | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.create_project.import_selected_repo | |||
</Button> | |||
</div> | |||
} | |||
showBreadcrumb={true} | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.from_bbs | |||
</span> | |||
} | |||
/> | |||
<Alert | |||
variant="error" | |||
> | |||
onboarding.create_project.no_bbs_binding | |||
</Alert> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
additionalActions={ | |||
<div | |||
className="display-flex-center pull-right" | |||
> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<Button | |||
className="button-large button-primary" | |||
disabled={true} | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.create_project.import_selected_repo | |||
</Button> | |||
</div> | |||
} | |||
showBreadcrumb={true} | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.from_bbs | |||
</span> | |||
} | |||
/> | |||
<i | |||
className="spinner" | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: pat form 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
additionalActions={false} | |||
showBreadcrumb={true} | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.from_bbs | |||
</span> | |||
} | |||
/> | |||
<BitbucketPersonalAccessTokenForm | |||
bitbucketSetting={ | |||
Object { | |||
"alm": "bitbucket", | |||
"key": "key", | |||
} | |||
} | |||
onPersonalAccessTokenCreate={[MockFunction]} | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: selected repo 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
additionalActions={ | |||
<div | |||
className="display-flex-center pull-right" | |||
> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<Button | |||
className="button-large button-primary" | |||
disabled={false} | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.create_project.import_selected_repo | |||
</Button> | |||
</div> | |||
} | |||
showBreadcrumb={true} | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.from_bbs | |||
</span> | |||
} | |||
/> | |||
<BitbucketImportRepositoryForm | |||
importing={false} | |||
onSelectRepository={[MockFunction]} | |||
projectRepositories={ | |||
Object { | |||
"foo": Array [ | |||
Object { | |||
"id": 1, | |||
"name": "Repo", | |||
"projectKey": "project", | |||
"slug": "project__repo", | |||
}, | |||
], | |||
} | |||
} | |||
projects={ | |||
Array [ | |||
Object { | |||
"id": 1, | |||
"key": "foo", | |||
"name": "Project", | |||
}, | |||
] | |||
} | |||
selectedRepository={ | |||
Object { | |||
"id": 1, | |||
"name": "Repo", | |||
"projectKey": "project", | |||
"slug": "project__repo", | |||
} | |||
} | |||
/> | |||
</Fragment> | |||
`; |
@@ -0,0 +1,267 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Fragment> | |||
<header | |||
className="huge-spacer-top big-spacer-bottom padded" | |||
> | |||
<h1 | |||
className="text-center huge big-spacer-bottom" | |||
> | |||
my_account.create_new.TRK | |||
</h1> | |||
<p | |||
className="text-center big" | |||
> | |||
onboarding.create_project.select_method | |||
</p> | |||
</header> | |||
<div | |||
className="create-project-modes huge-spacer-top display-flex-space-around" | |||
> | |||
<button | |||
className="button button-huge display-flex-column create-project-mode-type-manual" | |||
onClick={[Function]} | |||
type="button" | |||
> | |||
<img | |||
alt="" | |||
height={80} | |||
src="/images/sonarcloud/analysis/manual.svg" | |||
width={80} | |||
/> | |||
<div | |||
className="medium big-spacer-top" | |||
> | |||
onboarding.create_project.select_method.manual | |||
</div> | |||
</button> | |||
<button | |||
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs" | |||
disabled={false} | |||
onClick={[Function]} | |||
type="button" | |||
> | |||
<img | |||
alt="" | |||
height={80} | |||
src="/images/alm/bitbucket.svg" | |||
width={80} | |||
/> | |||
<div | |||
className="medium big-spacer-top" | |||
> | |||
onboarding.create_project.select_method.from_bbs | |||
</div> | |||
</button> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: loading bbs instances 1`] = ` | |||
<Fragment> | |||
<header | |||
className="huge-spacer-top big-spacer-bottom padded" | |||
> | |||
<h1 | |||
className="text-center huge big-spacer-bottom" | |||
> | |||
my_account.create_new.TRK | |||
</h1> | |||
<p | |||
className="text-center big" | |||
> | |||
onboarding.create_project.select_method | |||
</p> | |||
</header> | |||
<div | |||
className="create-project-modes huge-spacer-top display-flex-space-around" | |||
> | |||
<button | |||
className="button button-huge display-flex-column create-project-mode-type-manual" | |||
onClick={[Function]} | |||
type="button" | |||
> | |||
<img | |||
alt="" | |||
height={80} | |||
src="/images/sonarcloud/analysis/manual.svg" | |||
width={80} | |||
/> | |||
<div | |||
className="medium big-spacer-top" | |||
> | |||
onboarding.create_project.select_method.manual | |||
</div> | |||
</button> | |||
<button | |||
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs" | |||
disabled={false} | |||
onClick={[Function]} | |||
type="button" | |||
> | |||
<img | |||
alt="" | |||
height={80} | |||
src="/images/alm/bitbucket.svg" | |||
width={80} | |||
/> | |||
<div | |||
className="medium big-spacer-top" | |||
> | |||
onboarding.create_project.select_method.from_bbs | |||
</div> | |||
<span> | |||
onboarding.create_project.check_bbs_supported | |||
<i | |||
className="little-spacer-left spinner" | |||
/> | |||
</span> | |||
</button> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: no bbs instances 1`] = ` | |||
<Fragment> | |||
<header | |||
className="huge-spacer-top big-spacer-bottom padded" | |||
> | |||
<h1 | |||
className="text-center huge big-spacer-bottom" | |||
> | |||
my_account.create_new.TRK | |||
</h1> | |||
<p | |||
className="text-center big" | |||
> | |||
onboarding.create_project.select_method | |||
</p> | |||
</header> | |||
<div | |||
className="create-project-modes huge-spacer-top display-flex-space-around" | |||
> | |||
<button | |||
className="button button-huge display-flex-column create-project-mode-type-manual" | |||
onClick={[Function]} | |||
type="button" | |||
> | |||
<img | |||
alt="" | |||
height={80} | |||
src="/images/sonarcloud/analysis/manual.svg" | |||
width={80} | |||
/> | |||
<div | |||
className="medium big-spacer-top" | |||
> | |||
onboarding.create_project.select_method.manual | |||
</div> | |||
</button> | |||
<button | |||
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs" | |||
disabled={true} | |||
onClick={[Function]} | |||
type="button" | |||
> | |||
<img | |||
alt="" | |||
height={80} | |||
src="/images/alm/bitbucket.svg" | |||
width={80} | |||
/> | |||
<div | |||
className="medium big-spacer-top" | |||
> | |||
onboarding.create_project.select_method.from_bbs | |||
</div> | |||
<div | |||
className="text-muted small spacer-top" | |||
style={ | |||
Object { | |||
"lineHeight": 1.5, | |||
} | |||
} | |||
> | |||
onboarding.create_project.bbs_not_configured | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="onboarding.create_project.zero_bbs_instances" | |||
/> | |||
</div> | |||
</button> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: too many bbs instances 1`] = ` | |||
<Fragment> | |||
<header | |||
className="huge-spacer-top big-spacer-bottom padded" | |||
> | |||
<h1 | |||
className="text-center huge big-spacer-bottom" | |||
> | |||
my_account.create_new.TRK | |||
</h1> | |||
<p | |||
className="text-center big" | |||
> | |||
onboarding.create_project.select_method | |||
</p> | |||
</header> | |||
<div | |||
className="create-project-modes huge-spacer-top display-flex-space-around" | |||
> | |||
<button | |||
className="button button-huge display-flex-column create-project-mode-type-manual" | |||
onClick={[Function]} | |||
type="button" | |||
> | |||
<img | |||
alt="" | |||
height={80} | |||
src="/images/sonarcloud/analysis/manual.svg" | |||
width={80} | |||
/> | |||
<div | |||
className="medium big-spacer-top" | |||
> | |||
onboarding.create_project.select_method.manual | |||
</div> | |||
</button> | |||
<button | |||
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs" | |||
disabled={true} | |||
onClick={[Function]} | |||
type="button" | |||
> | |||
<img | |||
alt="" | |||
height={80} | |||
src="/images/alm/bitbucket.svg" | |||
width={80} | |||
/> | |||
<div | |||
className="medium big-spacer-top" | |||
> | |||
onboarding.create_project.select_method.from_bbs | |||
</div> | |||
<div | |||
className="text-muted small spacer-top" | |||
style={ | |||
Object { | |||
"lineHeight": 1.5, | |||
} | |||
} | |||
> | |||
onboarding.create_project.bbs_not_configured | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="onboarding.create_project.too_many_bbs_instances_X.2" | |||
/> | |||
</div> | |||
</button> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -41,7 +41,7 @@ exports[`should render correctly for SonarQube 1`] = ` | |||
<A11ySkipTarget | |||
anchor="create_project_main" | |||
/> | |||
<Connect(withCurrentUser(whenLoggedIn(CreateProjectPageSonarQube))) | |||
<Connect(withCurrentUser(whenLoggedIn(Connect(withAppState(CreateProjectPageSonarQube))))) | |||
location={ | |||
Object { | |||
"action": "PUSH", |
@@ -0,0 +1,48 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: additional content 1`] = ` | |||
<header | |||
className="huge-spacer-bottom bordered-bottom overflow-hidden" | |||
> | |||
<h1 | |||
className="pull-left huge big-spacer-bottom" | |||
> | |||
Foo | |||
</h1> | |||
Bar | |||
</header> | |||
`; | |||
exports[`should render correctly: default 1`] = ` | |||
<header | |||
className="huge-spacer-bottom bordered-bottom overflow-hidden" | |||
> | |||
<h1 | |||
className="pull-left huge big-spacer-bottom" | |||
> | |||
Foo | |||
</h1> | |||
</header> | |||
`; | |||
exports[`should render correctly: with breadcrumb 1`] = ` | |||
<header | |||
className="huge-spacer-bottom bordered-bottom overflow-hidden" | |||
> | |||
<h1 | |||
className="pull-left huge big-spacer-bottom" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/projects/create" | |||
> | |||
my_account.create_new.TRK | |||
</Link> | |||
<span | |||
className="big-spacer-left big-spacer-right slash-separator" | |||
/> | |||
Foo | |||
</h1> | |||
</header> | |||
`; |
@@ -0,0 +1,114 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<Fragment> | |||
<Helmet | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="my_account.create_new.TRK" | |||
titleTemplate="%s" | |||
/> | |||
<div | |||
className="page page-limited huge-spacer-bottom position-relative" | |||
id="create-project" | |||
> | |||
<CreateProjectModeSelection | |||
bbsBindingCount={0} | |||
loadingBindings={true} | |||
onSelectMode={[Function]} | |||
/> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly if no branch support 1`] = ` | |||
<Fragment> | |||
<Helmet | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="my_account.create_new.TRK" | |||
titleTemplate="%s" | |||
/> | |||
<div | |||
className="page page-limited huge-spacer-bottom position-relative" | |||
id="create-project" | |||
> | |||
<ManualProjectCreate | |||
branchesEnabled={false} | |||
currentUser={ | |||
Object { | |||
"groups": Array [], | |||
"isLoggedIn": true, | |||
"login": "luke", | |||
"name": "Skywalker", | |||
"scmAccounts": Array [], | |||
} | |||
} | |||
onProjectCreate={[Function]} | |||
/> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly if the BBS method is selected 1`] = ` | |||
<Fragment> | |||
<Helmet | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="my_account.create_new.TRK" | |||
titleTemplate="%s" | |||
/> | |||
<div | |||
className="page page-limited huge-spacer-bottom position-relative" | |||
id="create-project" | |||
> | |||
<Connect(BitbucketProjectCreate) | |||
bitbucketSettings={Array []} | |||
loadingBindings={true} | |||
location={ | |||
Object { | |||
"action": "PUSH", | |||
"hash": "", | |||
"key": "key", | |||
"pathname": "/path", | |||
"query": Object { | |||
"mode": "bbs", | |||
}, | |||
"search": "", | |||
"state": Object {}, | |||
} | |||
} | |||
onProjectCreate={[Function]} | |||
/> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly if the manual method is selected 1`] = ` | |||
<Fragment> | |||
<Helmet | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="my_account.create_new.TRK" | |||
titleTemplate="%s" | |||
/> | |||
<div | |||
className="page page-limited huge-spacer-bottom position-relative" | |||
id="create-project" | |||
> | |||
<ManualProjectCreate | |||
branchesEnabled={true} | |||
currentUser={ | |||
Object { | |||
"groups": Array [], | |||
"isLoggedIn": true, | |||
"login": "luke", | |||
"name": "Skywalker", | |||
"scmAccounts": Array [], | |||
} | |||
} | |||
onProjectCreate={[Function]} | |||
/> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -1,85 +1,90 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="create-project" | |||
> | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
title="onboarding.create_project.setup_manually" | |||
/> | |||
<div | |||
className="flex-1 huge-spacer-right" | |||
className="create-project" | |||
> | |||
<form | |||
className="manual-project-create" | |||
onSubmit={[Function]} | |||
<div | |||
className="flex-1 huge-spacer-right" | |||
> | |||
<withRouter(OrganizationInput) | |||
onChange={[Function]} | |||
organization="" | |||
organizations={ | |||
Array [ | |||
Object { | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"key": "bar", | |||
"name": "Bar", | |||
"subscription": "PAID", | |||
}, | |||
] | |||
} | |||
/> | |||
<ValidationInput | |||
className="form-field" | |||
description="onboarding.create_project.project_key.description" | |||
help="onboarding.create_project.project_key.help" | |||
id="project-key" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.project_key" | |||
required={true} | |||
<form | |||
className="manual-project-create" | |||
onSubmit={[Function]} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-super-large" | |||
id="project-key" | |||
maxLength={400} | |||
minLength={1} | |||
<withRouter(OrganizationInput) | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
organization="" | |||
organizations={ | |||
Array [ | |||
Object { | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"key": "bar", | |||
"name": "Bar", | |||
"subscription": "PAID", | |||
}, | |||
] | |||
} | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
className="form-field" | |||
description="onboarding.create_project.display_name.description" | |||
help="onboarding.create_project.display_name.help" | |||
id="project-name" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.display_name" | |||
required={true} | |||
> | |||
<input | |||
className="input-super-large" | |||
<ValidationInput | |||
className="form-field" | |||
description="onboarding.create_project.project_key.description" | |||
help="onboarding.create_project.project_key.help" | |||
id="project-key" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.project_key" | |||
required={true} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-super-large" | |||
id="project-key" | |||
maxLength={400} | |||
minLength={1} | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
className="form-field" | |||
description="onboarding.create_project.display_name.description" | |||
help="onboarding.create_project.display_name.help" | |||
id="project-name" | |||
maxLength={255} | |||
minLength={1} | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.display_name" | |||
required={true} | |||
> | |||
<input | |||
className="input-super-large" | |||
id="project-name" | |||
maxLength={255} | |||
minLength={1} | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
set_up | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
set_up | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
</form> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -17,6 +17,21 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
#create-project { | |||
padding-top: 0 !important; | |||
} | |||
#create-project header { | |||
padding-top: 20px; | |||
} | |||
.white-page #create-project header { | |||
background-color: white; | |||
position: sticky; | |||
top: var(--globalNavHeight); | |||
z-index: var(--pageMainZIndex); | |||
} | |||
.create-project { | |||
display: flex !important; | |||
justify-content: space-between; | |||
@@ -124,3 +139,25 @@ | |||
.create-project-actions .icon-checkbox { | |||
margin-right: 8px; | |||
} | |||
.create-project-modes { | |||
margin: 0 auto; | |||
max-width: 500px; | |||
} | |||
.create-project-import-bbs i.icon-radio { | |||
flex-shrink: 0; | |||
} | |||
.create-project-import-bbs .open .boxed-group-header { | |||
border-bottom: 1px solid var(--barBorderColor); | |||
} | |||
.create-project-import-bbs .boxed-group-inner { | |||
padding-top: calc(3 * var(--gridSize)); | |||
} | |||
.create-project-import-bbs-repo { | |||
width: 250px; | |||
min-height: 40px; | |||
} |
@@ -0,0 +1,23 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
export enum CreateProjectModes { | |||
Manual = 'manual', | |||
BitbucketServer = 'bbs' | |||
} |
@@ -18,12 +18,24 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { | |||
AlmSettingsInstance, | |||
ALM_KEYS, | |||
AzureBindingDefinition, | |||
BitbucketBindingDefinition, | |||
GithubBindingDefinition, | |||
GitlabBindingDefinition | |||
} from '../../types/alm-settings'; | |||
export function mockAlmSettingsInstance( | |||
overrides: Partial<AlmSettingsInstance> = {} | |||
): AlmSettingsInstance { | |||
return { | |||
alm: ALM_KEYS.GITHUB, | |||
key: 'key', | |||
...overrides | |||
}; | |||
} | |||
export function mockAzureDefinition( | |||
overrides: Partial<AzureBindingDefinition> = {} | |||
): AzureBindingDefinition { |
@@ -40,6 +40,7 @@ close=Close | |||
closed=Closed | |||
code=Code | |||
color=Color | |||
collapse_all=Collapse all | |||
compare=Compare | |||
component=Component | |||
configure=Configure | |||
@@ -68,8 +69,9 @@ end_date=End Date | |||
edit=Edit | |||
events=Events | |||
example=Example | |||
extend=Extend | |||
expand_all=Expand all | |||
explore=Explore | |||
extend=Extend | |||
false=False | |||
favorite=Favorite | |||
file=File | |||
@@ -3059,7 +3061,7 @@ onboarding.project_analysis.suggestions.bitbucket_extra=In case you need it, the | |||
onboarding.project_analysis.suggestions.github=If you are using Travis CI, the SonarCloud Travis Add-on makes it easier to run these commands with your CI process. | |||
onboarding.create_project.header=Analyze projects | |||
onboarding.create_project.setup_manually=Set up manually | |||
onboarding.create_project.setup_manually=Create manually | |||
onboarding.create_project.create_new_org=Create another organization | |||
onboarding.create_project.import_new_org=Import another organization | |||
onboarding.create_project.install_app_description.bitbucket=We need you to install the SonarCloud Bitbucket application on one of your team in order to select which repositories you want to analyze. | |||
@@ -3081,13 +3083,40 @@ onboarding.create_project.display_name=Display name | |||
onboarding.create_project.display_name.error=The display name is required. | |||
onboarding.create_project.display_name.description=Up to 255 characters | |||
onboarding.create_project.display_name.help=Some scanners might override the value you provide. | |||
onboarding.create_project.repository_imported=Already imported: {link} | |||
onboarding.create_project.repository_imported=Already set up | |||
onboarding.create_project.see_project=See the project | |||
onboarding.create_project.select_repositories=Select repositories | |||
onboarding.create_project.select_all_repositories=Select all available repositories | |||
onboarding.create_project.subscribe_to_import_private_repositories=You need to subscribe your organization to a paid plan to import private projects | |||
onboarding.create_project.encourage_to_subscribe=Subscribe your organization to our paid plan to get unlimited private projects. | |||
onboarding.create_project.subscribtion_success_x={0} has been successfully upgraded to paid plan. You can now import and analyze private projects. | |||
onboarding.create_project.from_bbs=From Bitbucket Server | |||
onboarding.create_project.grant_access_to_bbs.title=Grant access to your repositories | |||
onboarding.create_project.grant_access_to_bbs.help=SonarQube needs a personal access token to access and list your repositories from Bitbucket Server. | |||
onboarding.create_project.select_method=How do you want to create your project? | |||
onboarding.create_project.select_method.manual=Manually | |||
onboarding.create_project.select_method.from_bbs=From a Bitbucket Server repository | |||
onboarding.create_project.check_bbs_supported=Checking if available | |||
onboarding.create_project.too_many_bbs_instances_X=You must have exactly 1 Bitbucket Server instance configured in order to use this method. You currently have {0}. | |||
onboarding.create_project.zero_bbs_instances=You must first configure a Bitbucket Server instance. | |||
onboarding.create_project.bbs_not_configured=This feature isn't available | |||
onboarding.create_project.no_bbs_binding=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method, but none were found. Either create the project manually, or contact your system administrator. | |||
onboarding.create_project.no_bbs_binding.admin=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method. You can configure instances under {url}. | |||
onboarding.create_project.enter_pat=Enter personal access token | |||
onboarding.create_project.pat_help.title=How to create a personal access token? | |||
onboarding.create_project.pat_help.bbs_help_1=Click the following link to generate a token in Bitbucket Server, and copy-paste it into the personal access token field. | |||
onboarding.create_project.pat_help.bbs_help_2=Set a name, for example "SonarQube", and select the following permissions: | |||
onboarding.create_project.pat_help.link=Create personal access token | |||
onboarding.create_project.pat_help.bbs_permission_projects=Projects: {perm} | |||
onboarding.create_project.pat_help.bbs_permission_repos=Repositories: {perm} | |||
onboarding.create_project.pat_help.read_permission=Read | |||
onboarding.create_project.error_fetching_bbs_projects=There was an error fetching the projects from Bitbucket Server. Contact your system administrator, or check your personal access token. | |||
onboarding.create_project.error_fetching_bbs_repos=There was an error fetching the repositories from Bitbucket Server. Contact your system administrator, or check your personal access token. | |||
onboarding.create_project.no_bbs_projects=No projects could be fetched from Bitbucket Server. Contact your system administrator, or check your personal access token. | |||
onboarding.create_project.no_bbs_repos=No repositories were found for this project. Contact your system administrator, or check your personal access token. | |||
onboarding.create_project.no_bbs_repos.filter=No repositories match your filter. | |||
onboarding.create_project.import_selected_repo=Set up selected repository | |||
onboarding.create_project.go_to_project=Go to project | |||
onboarding.create_organization.page.header=Create Organization | |||
onboarding.create_organization.page.description=An organization is a space where a team or a whole company can collaborate accross many projects. |