@@ -30,8 +30,14 @@ import { | |||
} from '../types/alm-integration'; | |||
import { ProjectBase } from './components'; | |||
export function setAlmPersonalAccessToken(almSetting: string, pat: string): Promise<void> { | |||
return post('/api/alm_integrations/set_pat', { almSetting, pat }).catch(throwGlobalError); | |||
export function setAlmPersonalAccessToken( | |||
almSetting: string, | |||
pat: string, | |||
username?: string | |||
): Promise<void> { | |||
return post('/api/alm_integrations/set_pat', { almSetting, pat, username }).catch( | |||
throwGlobalError | |||
); | |||
} | |||
export function checkPersonalAccessTokenIsValid( |
@@ -0,0 +1,97 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { WithRouterProps } from 'react-router'; | |||
import { BitbucketProjectRepositories, BitbucketRepository } from '../../../types/alm-integration'; | |||
import { AlmSettingsInstance } from '../../../types/alm-settings'; | |||
import BitbucketCloudProjectCreateRenderer from './BitbucketCloudProjectCreateRender'; | |||
interface Props extends Pick<WithRouterProps, 'location' | 'router'> { | |||
canAdmin: boolean; | |||
settings: AlmSettingsInstance[]; | |||
loadingBindings: boolean; | |||
onProjectCreate: (projectKeys: string[]) => void; | |||
} | |||
interface State { | |||
settings: AlmSettingsInstance; | |||
loading: boolean; | |||
projectRepositories?: BitbucketProjectRepositories; | |||
searchResults?: BitbucketRepository[]; | |||
selectedRepository?: BitbucketRepository; | |||
showPersonalAccessTokenForm: boolean; | |||
} | |||
export default class BitbucketCloudProjectCreate 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. | |||
settings: props.settings[0], | |||
loading: false, | |||
showPersonalAccessTokenForm: true | |||
}; | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchData(); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.settings.length === 0 && this.props.settings.length > 0) { | |||
this.setState({ settings: this.props.settings[0] }, () => this.fetchData()); | |||
} | |||
} | |||
handlePersonalAccessTokenCreated = async () => { | |||
this.setState({ showPersonalAccessTokenForm: false }); | |||
this.cleanUrl(); | |||
await this.fetchData(); | |||
}; | |||
cleanUrl = () => { | |||
const { location, router } = this.props; | |||
delete location.query.resetPat; | |||
router.replace(location); | |||
}; | |||
async fetchData() {} | |||
render() { | |||
const { canAdmin, loadingBindings, location } = this.props; | |||
const { settings, loading, showPersonalAccessTokenForm } = this.state; | |||
return ( | |||
<BitbucketCloudProjectCreateRenderer | |||
settings={settings} | |||
canAdmin={canAdmin} | |||
loading={loading || loadingBindings} | |||
onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated} | |||
resetPat={Boolean(location.query.resetPat)} | |||
showPersonalAccessTokenForm={ | |||
showPersonalAccessTokenForm || Boolean(location.query.resetPat) | |||
} | |||
/> | |||
); | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; | |||
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; | |||
import CreateProjectPageHeader from './CreateProjectPageHeader'; | |||
import PersonalAccessTokenForm from './PersonalAccessTokenForm'; | |||
import WrongBindingCountAlert from './WrongBindingCountAlert'; | |||
export interface BitbucketCloudProjectCreateRendererProps { | |||
settings?: AlmSettingsInstance; | |||
canAdmin?: boolean; | |||
loading: boolean; | |||
onPersonalAccessTokenCreated: () => void; | |||
resetPat: boolean; | |||
showPersonalAccessTokenForm: boolean; | |||
} | |||
export default function BitbucketCloudProjectCreateRenderer( | |||
props: BitbucketCloudProjectCreateRendererProps | |||
) { | |||
const { settings, canAdmin, loading, resetPat, showPersonalAccessTokenForm } = props; | |||
return ( | |||
<> | |||
<CreateProjectPageHeader | |||
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.bitbucketcloud.title')} | |||
</span> | |||
} | |||
/> | |||
{loading && <i className="spinner" />} | |||
{!loading && !settings && ( | |||
<WrongBindingCountAlert alm={AlmKeys.BitbucketCloud} canAdmin={!!canAdmin} /> | |||
)} | |||
{!loading && | |||
settings && | |||
(showPersonalAccessTokenForm ? ( | |||
<PersonalAccessTokenForm | |||
almSetting={settings} | |||
resetPat={resetPat} | |||
onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated} | |||
/> | |||
) : ( | |||
<p>Placeholder for next step</p> | |||
))} | |||
</> | |||
); | |||
} |
@@ -20,12 +20,10 @@ | |||
import * as React from 'react'; | |||
import { WithRouterProps } from 'react-router'; | |||
import { | |||
checkPersonalAccessTokenIsValid, | |||
getBitbucketServerProjects, | |||
getBitbucketServerRepositories, | |||
importBitbucketServerProject, | |||
searchForBitbucketServerRepositories, | |||
setAlmPersonalAccessToken | |||
searchForBitbucketServerRepositories | |||
} from '../../../api/alm-integrations'; | |||
import { | |||
BitbucketProject, | |||
@@ -36,7 +34,7 @@ import { AlmSettingsInstance } from '../../../types/alm-settings'; | |||
import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer'; | |||
import { DEFAULT_BBS_PAGE_SIZE } from './constants'; | |||
interface Props extends Pick<WithRouterProps, 'location'> { | |||
interface Props extends Pick<WithRouterProps, 'location' | 'router'> { | |||
canAdmin: boolean; | |||
bitbucketSettings: AlmSettingsInstance[]; | |||
loadingBindings: boolean; | |||
@@ -47,14 +45,12 @@ interface State { | |||
bitbucketSetting?: AlmSettingsInstance; | |||
importing: boolean; | |||
loading: boolean; | |||
patIsValid?: boolean; | |||
projects?: BitbucketProject[]; | |||
projectRepositories?: BitbucketProjectRepositories; | |||
searching: boolean; | |||
searchResults?: BitbucketRepository[]; | |||
selectedRepository?: BitbucketRepository; | |||
submittingToken?: boolean; | |||
tokenValidationFailed: boolean; | |||
showPersonalAccessTokenForm: boolean; | |||
} | |||
export default class BitbucketProjectCreate extends React.PureComponent<Props, State> { | |||
@@ -69,13 +65,12 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S | |||
importing: false, | |||
loading: false, | |||
searching: false, | |||
tokenValidationFailed: false | |||
showPersonalAccessTokenForm: true | |||
}; | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchInitialData(); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
@@ -91,38 +86,27 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S | |||
} | |||
fetchInitialData = async () => { | |||
this.setState({ loading: true }); | |||
const { showPersonalAccessTokenForm } = this.state; | |||
const patIsValid = await this.checkPersonalAccessToken().catch(() => false); | |||
if (!showPersonalAccessTokenForm) { | |||
this.setState({ loading: true }); | |||
const projects = await this.fetchBitbucketProjects().catch(() => undefined); | |||
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); | |||
let projectRepositories; | |||
if (projects && projects.length > 0) { | |||
projectRepositories = await this.fetchBitbucketRepositories(projects).catch( | |||
() => undefined | |||
); | |||
} | |||
if (this.mounted) { | |||
this.setState({ | |||
projects, | |||
projectRepositories, | |||
loading: false | |||
}); | |||
} | |||
} | |||
return checkPersonalAccessTokenIsValid(bitbucketSetting.key).then(({ status }) => status); | |||
}; | |||
fetchBitbucketProjects = (): Promise<BitbucketProject[] | undefined> => { | |||
@@ -184,29 +168,16 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S | |||
}); | |||
}; | |||
handlePersonalAccessTokenCreate = (token: string) => { | |||
const { bitbucketSetting } = this.state; | |||
if (!bitbucketSetting || token.length < 1) { | |||
return; | |||
} | |||
cleanUrl = () => { | |||
const { location, router } = this.props; | |||
delete location.query.resetPat; | |||
router.replace(location); | |||
}; | |||
this.setState({ submittingToken: true, tokenValidationFailed: false }); | |||
setAlmPersonalAccessToken(bitbucketSetting.key, token) | |||
.then(this.checkPersonalAccessToken) | |||
.then(patIsValid => { | |||
if (this.mounted) { | |||
this.setState({ submittingToken: false, patIsValid, tokenValidationFailed: !patIsValid }); | |||
if (patIsValid) { | |||
this.fetchInitialData(); | |||
} | |||
} | |||
}) | |||
.catch(() => { | |||
if (this.mounted) { | |||
this.setState({ submittingToken: false }); | |||
} | |||
}); | |||
handlePersonalAccessTokenCreated = async () => { | |||
this.setState({ showPersonalAccessTokenForm: false }); | |||
this.cleanUrl(); | |||
await this.fetchInitialData(); | |||
}; | |||
handleImportRepository = () => { | |||
@@ -271,14 +242,12 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S | |||
bitbucketSetting, | |||
importing, | |||
loading, | |||
patIsValid, | |||
projectRepositories, | |||
projects, | |||
searching, | |||
searchResults, | |||
selectedRepository, | |||
submittingToken, | |||
tokenValidationFailed | |||
showPersonalAccessTokenForm | |||
} = this.state; | |||
return ( | |||
@@ -288,18 +257,19 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S | |||
importing={importing} | |||
loading={loading || loadingBindings} | |||
onImportRepository={this.handleImportRepository} | |||
onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate} | |||
onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated} | |||
onProjectCreate={this.props.onProjectCreate} | |||
onSearch={this.handleSearch} | |||
onSelectRepository={this.handleSelectRepository} | |||
projectRepositories={projectRepositories} | |||
projects={projects} | |||
resetPat={Boolean(location.query.resetPat)} | |||
searchResults={searchResults} | |||
searching={searching} | |||
selectedRepository={selectedRepository} | |||
showPersonalAccessTokenForm={!patIsValid || Boolean(location.query.resetPat)} | |||
submittingToken={submittingToken} | |||
tokenValidationFailed={tokenValidationFailed} | |||
showPersonalAccessTokenForm={ | |||
showPersonalAccessTokenForm || Boolean(location.query.resetPat) | |||
} | |||
/> | |||
); | |||
} |
@@ -41,16 +41,15 @@ export interface BitbucketProjectCreateRendererProps { | |||
onImportRepository: () => void; | |||
onSearch: (query: string) => void; | |||
onSelectRepository: (repo: BitbucketRepository) => void; | |||
onPersonalAccessTokenCreate: (token: string) => void; | |||
onPersonalAccessTokenCreated: () => void; | |||
onProjectCreate: (projectKeys: string[]) => void; | |||
projects?: BitbucketProject[]; | |||
projectRepositories?: BitbucketProjectRepositories; | |||
resetPat: boolean; | |||
searching: boolean; | |||
searchResults?: BitbucketRepository[]; | |||
selectedRepository?: BitbucketRepository; | |||
showPersonalAccessTokenForm?: boolean; | |||
submittingToken?: boolean; | |||
tokenValidationFailed: boolean; | |||
} | |||
export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCreateRendererProps) { | |||
@@ -65,8 +64,7 @@ export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCr | |||
searching, | |||
searchResults, | |||
showPersonalAccessTokenForm, | |||
submittingToken, | |||
tokenValidationFailed | |||
resetPat | |||
} = props; | |||
return ( | |||
@@ -109,9 +107,8 @@ export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCr | |||
(showPersonalAccessTokenForm ? ( | |||
<PersonalAccessTokenForm | |||
almSetting={bitbucketSetting} | |||
onPersonalAccessTokenCreate={props.onPersonalAccessTokenCreate} | |||
submitting={submittingToken} | |||
validationFailed={tokenValidationFailed} | |||
onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated} | |||
resetPat={resetPat} | |||
/> | |||
) : ( | |||
<BitbucketImportRepositoryForm |
@@ -28,6 +28,7 @@ import { withAppState } from '../../../components/hoc/withAppState'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; | |||
import AzureProjectCreate from './AzureProjectCreate'; | |||
import BitbucketCloudProjectCreate from './BitbucketCloudProjectCreate'; | |||
import BitbucketProjectCreate from './BitbucketProjectCreate'; | |||
import CreateProjectModeSelection from './CreateProjectModeSelection'; | |||
import GitHubProjectCreate from './GitHubProjectCreate'; | |||
@@ -44,6 +45,7 @@ interface Props extends Pick<WithRouterProps, 'router' | 'location'> { | |||
interface State { | |||
azureSettings: AlmSettingsInstance[]; | |||
bitbucketSettings: AlmSettingsInstance[]; | |||
bitbucketCloudSettings: AlmSettingsInstance[]; | |||
githubSettings: AlmSettingsInstance[]; | |||
gitlabSettings: AlmSettingsInstance[]; | |||
loading: boolean; | |||
@@ -54,6 +56,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { | |||
state: State = { | |||
azureSettings: [], | |||
bitbucketSettings: [], | |||
bitbucketCloudSettings: [], | |||
githubSettings: [], | |||
gitlabSettings: [], | |||
loading: true | |||
@@ -76,6 +79,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { | |||
this.setState({ | |||
azureSettings: almSettings.filter(s => s.alm === AlmKeys.Azure), | |||
bitbucketSettings: almSettings.filter(s => s.alm === AlmKeys.BitbucketServer), | |||
bitbucketCloudSettings: almSettings.filter(s => s.alm === AlmKeys.BitbucketCloud), | |||
githubSettings: almSettings.filter(s => s.alm === AlmKeys.GitHub), | |||
gitlabSettings: almSettings.filter(s => s.alm === AlmKeys.GitLab), | |||
loading: false | |||
@@ -112,6 +116,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { | |||
const { | |||
azureSettings, | |||
bitbucketSettings, | |||
bitbucketCloudSettings, | |||
githubSettings, | |||
gitlabSettings, | |||
loading | |||
@@ -138,6 +143,19 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { | |||
loadingBindings={loading} | |||
location={location} | |||
onProjectCreate={this.handleProjectCreate} | |||
router={router} | |||
/> | |||
); | |||
} | |||
case CreateProjectModes.BitbucketCloud: { | |||
return ( | |||
<BitbucketCloudProjectCreate | |||
canAdmin={!!canAdmin} | |||
loadingBindings={loading} | |||
location={location} | |||
onProjectCreate={this.handleProjectCreate} | |||
router={router} | |||
settings={bitbucketCloudSettings} | |||
/> | |||
); | |||
} |
@@ -19,15 +19,9 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { WithRouterProps } from 'react-router'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { | |||
checkPersonalAccessTokenIsValid, | |||
getGitlabProjects, | |||
importGitlabProject, | |||
setAlmPersonalAccessToken | |||
} from '../../../api/alm-integrations'; | |||
import { getGitlabProjects, importGitlabProject } from '../../../api/alm-integrations'; | |||
import { GitlabProject } from '../../../types/alm-integration'; | |||
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; | |||
import { AlmSettingsInstance } from '../../../types/alm-settings'; | |||
import GitlabProjectCreateRenderer from './GitlabProjectCreateRenderer'; | |||
interface Props extends Pick<WithRouterProps, 'location' | 'router'> { | |||
@@ -43,12 +37,11 @@ interface State { | |||
loadingMore: boolean; | |||
projects?: GitlabProject[]; | |||
projectsPaging: T.Paging; | |||
submittingToken: boolean; | |||
tokenIsValid: boolean; | |||
tokenValidationErrorMessage?: string; | |||
resetPat: boolean; | |||
searching: boolean; | |||
searchQuery: string; | |||
settings?: AlmSettingsInstance; | |||
showPersonalAccessTokenForm: boolean; | |||
} | |||
const GITLAB_PROJECTS_PAGESIZE = 30; | |||
@@ -63,17 +56,16 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat | |||
loading: false, | |||
loadingMore: false, | |||
projectsPaging: { pageIndex: 1, total: 0, pageSize: GITLAB_PROJECTS_PAGESIZE }, | |||
tokenIsValid: false, | |||
resetPat: false, | |||
showPersonalAccessTokenForm: true, | |||
searching: false, | |||
searchQuery: '', | |||
settings: props.settings.length === 1 ? props.settings[0] : undefined, | |||
submittingToken: false | |||
settings: props.settings.length === 1 ? props.settings[0] : undefined | |||
}; | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchInitialData(); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
@@ -90,50 +82,30 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat | |||
} | |||
fetchInitialData = async () => { | |||
this.setState({ loading: true }); | |||
const { showPersonalAccessTokenForm } = this.state; | |||
const { status, error } = await this.checkPersonalAccessToken(); | |||
let result; | |||
if (status) { | |||
result = await this.fetchProjects(); | |||
} | |||
if (this.mounted) { | |||
if (result) { | |||
if (!showPersonalAccessTokenForm) { | |||
this.setState({ loading: true }); | |||
const result = await this.fetchProjects(); | |||
if (this.mounted && result) { | |||
const { projects, projectsPaging } = result; | |||
this.setState({ | |||
tokenIsValid: status, | |||
loading: false, | |||
projects, | |||
projectsPaging | |||
}); | |||
} else { | |||
this.setState({ | |||
loading: false, | |||
tokenValidationErrorMessage: !status ? error : undefined | |||
loading: false | |||
}); | |||
} | |||
} | |||
}; | |||
checkPersonalAccessToken = () => { | |||
const { settings } = this.state; | |||
if (!settings) { | |||
return Promise.resolve({ | |||
status: false, | |||
error: translate('onboarding.create_project.pat_incorrect', AlmKeys.GitLab) | |||
}); | |||
} | |||
return checkPersonalAccessTokenIsValid(settings.key); | |||
}; | |||
handleError = () => { | |||
if (this.mounted) { | |||
this.setState({ tokenIsValid: false }); | |||
this.setState({ resetPat: true, showPersonalAccessTokenForm: true }); | |||
} | |||
return undefined; | |||
@@ -141,7 +113,6 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat | |||
fetchProjects = async (pageIndex = 1, query?: string) => { | |||
const { settings } = this.state; | |||
if (!settings) { | |||
return Promise.resolve(undefined); | |||
} | |||
@@ -228,37 +199,10 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat | |||
router.replace(location); | |||
}; | |||
handlePersonalAccessTokenCreate = async (token: string) => { | |||
const { settings } = this.state; | |||
if (!settings || token.length < 1) { | |||
return; | |||
} | |||
this.setState({ submittingToken: true, tokenValidationErrorMessage: undefined }); | |||
try { | |||
await setAlmPersonalAccessToken(settings.key, token); | |||
const { status, error } = await this.checkPersonalAccessToken(); | |||
if (this.mounted) { | |||
this.setState({ | |||
submittingToken: false, | |||
tokenIsValid: status, | |||
tokenValidationErrorMessage: error | |||
}); | |||
if (status) { | |||
this.cleanUrl(); | |||
await this.fetchInitialData(); | |||
} | |||
} | |||
} catch (e) { | |||
if (this.mounted) { | |||
this.setState({ submittingToken: false }); | |||
} | |||
} | |||
handlePersonalAccessTokenCreated = async () => { | |||
this.setState({ showPersonalAccessTokenForm: false, resetPat: false }); | |||
this.cleanUrl(); | |||
await this.fetchInitialData(); | |||
}; | |||
render() { | |||
@@ -269,12 +213,11 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat | |||
loadingMore, | |||
projects, | |||
projectsPaging, | |||
tokenIsValid, | |||
resetPat, | |||
searching, | |||
searchQuery, | |||
settings, | |||
submittingToken, | |||
tokenValidationErrorMessage | |||
showPersonalAccessTokenForm | |||
} = this.state; | |||
return ( | |||
@@ -286,15 +229,16 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat | |||
loadingMore={loadingMore} | |||
onImport={this.handleImport} | |||
onLoadMore={this.handleLoadMore} | |||
onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate} | |||
onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated} | |||
onSearch={this.handleSearch} | |||
projects={projects} | |||
projectsPaging={projectsPaging} | |||
resetPat={resetPat || Boolean(location.query.resetPat)} | |||
searching={searching} | |||
searchQuery={searchQuery} | |||
showPersonalAccessTokenForm={!tokenIsValid || Boolean(location.query.resetPat)} | |||
submittingToken={submittingToken} | |||
tokenValidationErrorMessage={tokenValidationErrorMessage} | |||
showPersonalAccessTokenForm={ | |||
showPersonalAccessTokenForm || Boolean(location.query.resetPat) | |||
} | |||
/> | |||
); | |||
} |
@@ -34,16 +34,15 @@ export interface GitlabProjectCreateRendererProps { | |||
loadingMore: boolean; | |||
onImport: (gitlabProjectId: string) => void; | |||
onLoadMore: () => void; | |||
onPersonalAccessTokenCreate: (pat: string) => void; | |||
onPersonalAccessTokenCreated: () => void; | |||
onSearch: (searchQuery: string) => void; | |||
projects?: GitlabProject[]; | |||
projectsPaging: T.Paging; | |||
resetPat: boolean; | |||
searching: boolean; | |||
searchQuery: string; | |||
settings?: AlmSettingsInstance; | |||
showPersonalAccessTokenForm?: boolean; | |||
submittingToken?: boolean; | |||
tokenValidationErrorMessage?: string; | |||
} | |||
export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRendererProps) { | |||
@@ -54,12 +53,11 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe | |||
loadingMore, | |||
projects, | |||
projectsPaging, | |||
resetPat, | |||
searching, | |||
searchQuery, | |||
settings, | |||
showPersonalAccessTokenForm, | |||
submittingToken, | |||
tokenValidationErrorMessage | |||
showPersonalAccessTokenForm | |||
} = props; | |||
return ( | |||
@@ -89,10 +87,8 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe | |||
(showPersonalAccessTokenForm ? ( | |||
<PersonalAccessTokenForm | |||
almSetting={settings} | |||
onPersonalAccessTokenCreate={props.onPersonalAccessTokenCreate} | |||
submitting={submittingToken} | |||
validationFailed={Boolean(tokenValidationErrorMessage)} | |||
validationErrorMessage={tokenValidationErrorMessage} | |||
resetPat={resetPat} | |||
onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated} | |||
/> | |||
) : ( | |||
<GitlabProjectSelectionForm |
@@ -26,19 +26,33 @@ 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 { | |||
checkPersonalAccessTokenIsValid, | |||
setAlmPersonalAccessToken | |||
} from '../../../api/alm-integrations'; | |||
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; | |||
export interface PersonalAccessTokenFormProps { | |||
interface Props { | |||
almSetting: AlmSettingsInstance; | |||
onPersonalAccessTokenCreate: (token: string) => void; | |||
submitting?: boolean; | |||
resetPat: boolean; | |||
onPersonalAccessTokenCreated: () => void; | |||
} | |||
interface State { | |||
validationFailed: boolean; | |||
validationErrorMessage?: string; | |||
touched: boolean; | |||
password: string; | |||
username?: string; | |||
submitting: boolean; | |||
checkingPat: boolean; | |||
} | |||
function getPatUrl(alm: AlmKeys, url: string) { | |||
function getPatUrl(alm: AlmKeys, url = '') { | |||
if (alm === AlmKeys.BitbucketServer) { | |||
return `${url.replace(/\/$/, '')}/plugins/servlet/access-tokens/add`; | |||
} else if (alm === AlmKeys.BitbucketCloud) { | |||
return 'https://bitbucket.org/account/settings/app-passwords/new'; | |||
} else { | |||
// GitLab | |||
return url.endsWith('/api/v4') | |||
@@ -47,97 +61,236 @@ function getPatUrl(alm: AlmKeys, url: string) { | |||
} | |||
} | |||
export default function PersonalAccessTokenForm(props: PersonalAccessTokenFormProps) { | |||
const { | |||
almSetting: { alm, url }, | |||
submitting = false, | |||
validationFailed, | |||
validationErrorMessage | |||
} = props; | |||
const [touched, setTouched] = React.useState(false); | |||
React.useEffect(() => { | |||
setTouched(false); | |||
}, [submitting]); | |||
const isInvalid = validationFailed && !touched; | |||
const errorMessage = | |||
validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect', alm); | |||
return ( | |||
<div className="display-flex-start"> | |||
<form | |||
className="width-50" | |||
onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => { | |||
e.preventDefault(); | |||
const value = new FormData(e.currentTarget).get('personal_access_token') as string; | |||
props.onPersonalAccessTokenCreate(value); | |||
}}> | |||
<h2 className="big">{translate('onboarding.create_project.pat_form.title', alm)}</h2> | |||
<p className="big-spacer-top big-spacer-bottom"> | |||
{translate('onboarding.create_project.pat_form.help', alm)} | |||
</p> | |||
<ValidationInput | |||
error={isInvalid ? errorMessage : undefined} | |||
id="personal_access_token" | |||
isInvalid={isInvalid} | |||
isValid={false} | |||
label={translate('onboarding.create_project.enter_pat')} | |||
required={true}> | |||
<input | |||
autoFocus={true} | |||
className={classNames('input-super-large', { | |||
'is-invalid': isInvalid | |||
})} | |||
id="personal_access_token" | |||
minLength={1} | |||
name="personal_access_token" | |||
onChange={() => { | |||
setTouched(true); | |||
}} | |||
type="text" | |||
/> | |||
</ValidationInput> | |||
<SubmitButton disabled={isInvalid || submitting || !touched}> | |||
{translate('save')} | |||
</SubmitButton> | |||
<DeferredSpinner className="spacer-left" loading={submitting} /> | |||
</form> | |||
<Alert className="big-spacer-left width-50" display="block" variant="info"> | |||
<h3>{translate('onboarding.create_project.pat_help.title')}</h3> | |||
<p className="big-spacer-top big-spacer-bottom"> | |||
<FormattedMessage | |||
id="onboarding.create_project.pat_help.instructions" | |||
defaultMessage={translate('onboarding.create_project.pat_help.instructions')} | |||
values={{ alm: translate('onboarding.alm', alm) }} | |||
/> | |||
</p> | |||
{url && ( | |||
<div className="text-middle"> | |||
<img | |||
alt="" // Should be ignored by screen readers | |||
className="spacer-right" | |||
height="16" | |||
src={`${getBaseUrl()}/images/alm/${alm}.svg`} | |||
export default class PersonalAccessTokenForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
checkingPat: false, | |||
touched: false, | |||
password: '', | |||
submitting: false, | |||
validationFailed: false | |||
}; | |||
} | |||
async componentDidMount() { | |||
const { | |||
almSetting: { key }, | |||
resetPat | |||
} = this.props; | |||
this.mounted = true; | |||
// We don't need to check PAT if we want to reset | |||
if (!resetPat) { | |||
this.setState({ checkingPat: true }); | |||
const { patIsValid, error } = await checkPersonalAccessTokenIsValid(key) | |||
.then(({ status, error }) => ({ patIsValid: status, error })) | |||
.catch(() => ({ patIsValid: status, error: translate('default_error_message') })); | |||
if (patIsValid) { | |||
this.props.onPersonalAccessTokenCreated(); | |||
} | |||
if (this.mounted) { | |||
// This is the initial message when no token was provided | |||
if (error === `personal access token for '${key}' is missing`) { | |||
this.setState({ | |||
checkingPat: false | |||
}); | |||
} else { | |||
this.setState({ | |||
checkingPat: false, | |||
validationFailed: true, | |||
validationErrorMessage: error | |||
}); | |||
} | |||
} | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ | |||
touched: true, | |||
username: event.target.value | |||
}); | |||
}; | |||
handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ | |||
touched: true, | |||
password: event.target.value | |||
}); | |||
}; | |||
handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => { | |||
const { password, username } = this.state; | |||
const { | |||
almSetting: { key } | |||
} = this.props; | |||
e.preventDefault(); | |||
if (password) { | |||
this.setState({ submitting: true }); | |||
await setAlmPersonalAccessToken(key, password, username).catch(() => { | |||
/* Set will not check pat validity. We need to check again so we will catch issue after */ | |||
}); | |||
const { status, error } = await checkPersonalAccessTokenIsValid(key) | |||
.then(({ status, error }) => ({ status, error })) | |||
.catch(() => ({ status: false, error: translate('default_error_message') })); | |||
if (this.mounted && status) { | |||
// Let's reset status, | |||
this.setState({ | |||
checkingPat: false, | |||
touched: false, | |||
password: '', | |||
submitting: false, | |||
username: '', | |||
validationFailed: false | |||
}); | |||
this.props.onPersonalAccessTokenCreated(); | |||
} else if (this.mounted) { | |||
this.setState({ | |||
submitting: false, | |||
touched: false, | |||
validationFailed: true, | |||
validationErrorMessage: error | |||
}); | |||
} | |||
} | |||
}; | |||
render() { | |||
const { | |||
almSetting: { alm, url } | |||
} = this.props; | |||
const { | |||
checkingPat, | |||
submitting, | |||
touched, | |||
password, | |||
username, | |||
validationFailed, | |||
validationErrorMessage | |||
} = this.state; | |||
if (checkingPat) { | |||
return <DeferredSpinner className="spacer-left" loading={true} />; | |||
} | |||
const suffixTranslationKey = alm === AlmKeys.BitbucketCloud ? '.bitbucketcloud' : ''; | |||
const isInvalid = validationFailed && !touched; | |||
const canSubmit = Boolean(password) && (alm !== AlmKeys.BitbucketCloud || Boolean(username)); | |||
const submitButtonDiabled = isInvalid || submitting || !canSubmit; | |||
const errorMessage = | |||
validationErrorMessage ?? translate('onboarding.create_project.pat_incorrect', alm); | |||
return ( | |||
<div className="display-flex-start"> | |||
<form className="width-50" onSubmit={this.handleSubmit}> | |||
<h2 className="big">{translate('onboarding.create_project.pat_form.title', alm)}</h2> | |||
<p className="big-spacer-top big-spacer-bottom"> | |||
{translate('onboarding.create_project.pat_form.help', alm)} | |||
</p> | |||
{alm === AlmKeys.BitbucketCloud && ( | |||
<ValidationInput | |||
error={undefined} | |||
id="enter_username_validation" | |||
isInvalid={false} | |||
isValid={false} | |||
label={translate('onboarding.create_project.enter_username')} | |||
required={true}> | |||
<input | |||
autoFocus={true} | |||
className={classNames('input-super-large', { | |||
'is-invalid': isInvalid | |||
})} | |||
id="username" | |||
minLength={1} | |||
name="username" | |||
value={username} | |||
onChange={this.handleUsernameChange} | |||
type="text" | |||
/> | |||
</ValidationInput> | |||
)} | |||
<ValidationInput | |||
error={errorMessage} | |||
id="personal_access_token_validation" | |||
isInvalid={false} | |||
isValid={false} | |||
label={translate(`onboarding.create_project.enter_pat${suffixTranslationKey}`)} | |||
required={true}> | |||
<input | |||
autoFocus={alm !== AlmKeys.BitbucketCloud} | |||
className={classNames('input-super-large', { | |||
'is-invalid': isInvalid | |||
})} | |||
id="personal_access_token" | |||
minLength={1} | |||
value={password} | |||
onChange={this.handlePasswordChange} | |||
type="text" | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
error={errorMessage} | |||
id="personal_access_token_submit" | |||
isInvalid={isInvalid} | |||
isValid={false} | |||
label={null}> | |||
<SubmitButton disabled={submitButtonDiabled}>{translate('save')}</SubmitButton> | |||
<DeferredSpinner className="spacer-left" loading={submitting} /> | |||
</ValidationInput> | |||
</form> | |||
<Alert className="big-spacer-left width-50" display="block" variant="info"> | |||
<h3>{translate(`onboarding.create_project.pat_help${suffixTranslationKey}.title`)}</h3> | |||
<p className="big-spacer-top big-spacer-bottom"> | |||
<FormattedMessage | |||
id="onboarding.create_project.pat_help.instructions" | |||
defaultMessage={translate( | |||
`onboarding.create_project.pat_help${suffixTranslationKey}.instructions` | |||
)} | |||
values={{ alm: translate('onboarding.alm', alm) }} | |||
/> | |||
<a href={getPatUrl(alm, url)} 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.instructions2', alm)} | |||
</p> | |||
<ul> | |||
{alm === AlmKeys.BitbucketServer && ( | |||
<> | |||
</p> | |||
{(url || alm === AlmKeys.BitbucketCloud) && ( | |||
<div className="text-middle"> | |||
<img | |||
alt="" // Should be ignored by screen readers | |||
className="spacer-right" | |||
height="16" | |||
src={`${getBaseUrl()}/images/alm/${ | |||
alm === AlmKeys.BitbucketCloud ? AlmKeys.BitbucketServer : alm | |||
}.svg`} | |||
/> | |||
<a href={getPatUrl(alm, url)} rel="noopener noreferrer" target="_blank"> | |||
{translate(`onboarding.create_project.pat_help${suffixTranslationKey}.link`)} | |||
</a> | |||
</div> | |||
)} | |||
<p className="big-spacer-top big-spacer-bottom"> | |||
{translate('onboarding.create_project.pat_help.instructions2', alm)} | |||
</p> | |||
<ul> | |||
{alm === AlmKeys.BitbucketServer && ( | |||
<li> | |||
<FormattedMessage | |||
defaultMessage={translate( | |||
@@ -153,6 +306,8 @@ export default function PersonalAccessTokenForm(props: PersonalAccessTokenFormPr | |||
}} | |||
/> | |||
</li> | |||
)} | |||
{(alm === AlmKeys.BitbucketServer || alm === AlmKeys.BitbucketCloud) && ( | |||
<li> | |||
<FormattedMessage | |||
defaultMessage={translate( | |||
@@ -168,17 +323,18 @@ export default function PersonalAccessTokenForm(props: PersonalAccessTokenFormPr | |||
}} | |||
/> | |||
</li> | |||
</> | |||
)} | |||
{alm === AlmKeys.GitLab && ( | |||
<li className="spacer-bottom"> | |||
<strong> | |||
{translate('onboarding.create_project.pat_help.gitlab.read_api_permission')} | |||
</strong> | |||
</li> | |||
)} | |||
</ul> | |||
</Alert> | |||
</div> | |||
); | |||
)} | |||
{alm === AlmKeys.GitLab && ( | |||
<li className="spacer-bottom"> | |||
<strong> | |||
{translate('onboarding.create_project.pat_help.gitlab.read_api_permission')} | |||
</strong> | |||
</li> | |||
)} | |||
</ul> | |||
</Alert> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,65 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 } from '../../../../api/alm-integrations'; | |||
import { mockBitbucketCloudAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||
import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; | |||
import BitbucketCloudProjectCreate from '../BitbucketCloudProjectCreate'; | |||
jest.mock('../../../../api/alm-integrations', () => { | |||
return { | |||
checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue({ status: true }), | |||
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null) | |||
}; | |||
}); | |||
it('Should render correctly', async () => { | |||
let wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockRejectedValueOnce({}); | |||
wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot('Need App password'); | |||
}); | |||
it('Should handle app password correctly', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
await wrapper.instance().handlePersonalAccessTokenCreated(); | |||
expect(wrapper.state().showPersonalAccessTokenForm).toBe(false); | |||
}); | |||
function shallowRender(props?: Partial<BitbucketCloudProjectCreate['props']>) { | |||
return shallow<BitbucketCloudProjectCreate>( | |||
<BitbucketCloudProjectCreate | |||
onProjectCreate={jest.fn()} | |||
loadingBindings={false} | |||
location={mockLocation()} | |||
canAdmin={true} | |||
router={mockRouter()} | |||
settings={[mockBitbucketCloudAlmSettingsInstance()]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,49 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { mockBitbucketCloudAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||
import BitbucketCloudProjectCreateRenderer, { | |||
BitbucketCloudProjectCreateRendererProps | |||
} from '../BitbucketCloudProjectCreateRender'; | |||
it('Should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(shallowRender({ settings: undefined })).toMatchSnapshot('Wrong config'); | |||
expect(shallowRender({ loading: true })).toMatchSnapshot('Loading...'); | |||
expect( | |||
shallowRender({ | |||
showPersonalAccessTokenForm: true | |||
}) | |||
).toMatchSnapshot('Need App password'); | |||
}); | |||
function shallowRender(props?: Partial<BitbucketCloudProjectCreateRendererProps>) { | |||
return shallow( | |||
<BitbucketCloudProjectCreateRenderer | |||
onPersonalAccessTokenCreated={jest.fn()} | |||
loading={false} | |||
settings={mockBitbucketCloudAlmSettingsInstance()} | |||
resetPat={false} | |||
showPersonalAccessTokenForm={false} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -21,16 +21,17 @@ import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { | |||
checkPersonalAccessTokenIsValid, | |||
getBitbucketServerProjects, | |||
getBitbucketServerRepositories, | |||
importBitbucketServerProject, | |||
searchForBitbucketServerRepositories, | |||
setAlmPersonalAccessToken | |||
searchForBitbucketServerRepositories | |||
} from '../../../../api/alm-integrations'; | |||
import { mockBitbucketRepository } from '../../../../helpers/mocks/alm-integrations'; | |||
import { | |||
mockBitbucketProject, | |||
mockBitbucketRepository | |||
} from '../../../../helpers/mocks/alm-integrations'; | |||
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||
import { mockLocation } from '../../../../helpers/testMocks'; | |||
import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; | |||
import { AlmKeys } from '../../../../types/alm-settings'; | |||
import BitbucketProjectCreate from '../BitbucketProjectCreate'; | |||
@@ -39,7 +40,6 @@ jest.mock('../../../../api/alm-integrations', () => { | |||
'../../../../helpers/mocks/alm-integrations' | |||
); | |||
return { | |||
checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue({ status: true }), | |||
getBitbucketServerProjects: jest.fn().mockResolvedValue({ | |||
projects: [ | |||
mockBitbucketProject({ key: 'project1', name: 'Project 1' }), | |||
@@ -53,7 +53,6 @@ jest.mock('../../../../api/alm-integrations', () => { | |||
] | |||
}), | |||
importBitbucketServerProject: jest.fn().mockResolvedValue({ project: { key: 'baz' } }), | |||
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null), | |||
searchForBitbucketServerRepositories: jest.fn().mockResolvedValue({ | |||
repositories: [ | |||
mockBitbucketRepository(), | |||
@@ -65,50 +64,21 @@ jest.mock('../../../../api/alm-integrations', () => { | |||
beforeEach(jest.clearAllMocks); | |||
it('should render correctly', () => { | |||
it('should render correctly', async () => { | |||
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({ status: true }); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalled(); | |||
expect(wrapper.state().patIsValid).toBe(true); | |||
}); | |||
expect(shallowRender({ bitbucketSettings: [] })).toMatchSnapshot('No setting'); | |||
it('should correctly handle an invalid PAT', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: false }); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalled(); | |||
expect(wrapper.state().patIsValid).toBe(false); | |||
}); | |||
it('should correctly handle setting a new PAT', async () => { | |||
const wrapper = shallowRender(); | |||
wrapper.instance().handlePersonalAccessTokenCreate('token'); | |||
expect(setAlmPersonalAccessToken).toBeCalledWith('foo', 'token'); | |||
expect(wrapper.state().submittingToken).toBe(true); | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: false }); | |||
await waitAndUpdate(wrapper); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalled(); | |||
expect(wrapper.state().submittingToken).toBe(false); | |||
expect(wrapper.state().tokenValidationFailed).toBe(true); | |||
(getBitbucketServerRepositories as jest.Mock).mockRejectedValueOnce({}); | |||
await wrapper.instance().handlePersonalAccessTokenCreated(); | |||
expect(wrapper).toMatchSnapshot('No repository'); | |||
}); | |||
it('should correctly fetch projects and repos', async () => { | |||
const wrapper = shallowRender(); | |||
await wrapper.instance().handlePersonalAccessTokenCreated(); | |||
// Opens first project on mount. | |||
await waitAndUpdate(wrapper); | |||
expect(getBitbucketServerProjects).toBeCalledWith('foo'); | |||
expect(wrapper.state().projects).toHaveLength(2); | |||
@@ -159,6 +129,17 @@ it('should correctly handle search', async () => { | |||
expect(wrapper.state().searchResults).toHaveLength(2); | |||
}); | |||
it('should behave correctly when no setting', async () => { | |||
const wrapper = shallowRender({ bitbucketSettings: [] }); | |||
await wrapper.instance().handleSearch(''); | |||
await wrapper.instance().handleImportRepository(); | |||
await wrapper.instance().fetchBitbucketRepositories([mockBitbucketProject()]); | |||
expect(searchForBitbucketServerRepositories).not.toHaveBeenCalled(); | |||
expect(importBitbucketServerProject).not.toHaveBeenCalled(); | |||
expect(getBitbucketServerRepositories).not.toHaveBeenCalled(); | |||
}); | |||
function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) { | |||
return shallow<BitbucketProjectCreate>( | |||
<BitbucketProjectCreate | |||
@@ -166,6 +147,7 @@ function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) { | |||
bitbucketSettings={[mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer, key: 'foo' })]} | |||
loadingBindings={false} | |||
location={mockLocation()} | |||
router={mockRouter()} | |||
onProjectCreate={jest.fn()} | |||
{...props} | |||
/> |
@@ -52,14 +52,14 @@ function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {}) | |||
importing={false} | |||
loading={false} | |||
onImportRepository={jest.fn()} | |||
onPersonalAccessTokenCreate={jest.fn()} | |||
onPersonalAccessTokenCreated={jest.fn()} | |||
onProjectCreate={jest.fn()} | |||
onSearch={jest.fn()} | |||
onSelectRepository={jest.fn()} | |||
projectRepositories={{ foo: { allShown: true, repositories: [mockBitbucketRepository()] } }} | |||
projects={[mockBitbucketProject({ key: 'foo' })]} | |||
resetPat={false} | |||
searching={false} | |||
tokenValidationFailed={false} | |||
{...props} | |||
/> | |||
); |
@@ -20,12 +20,7 @@ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { | |||
checkPersonalAccessTokenIsValid, | |||
getGitlabProjects, | |||
importGitlabProject, | |||
setAlmPersonalAccessToken | |||
} from '../../../../api/alm-integrations'; | |||
import { getGitlabProjects, importGitlabProject } from '../../../../api/alm-integrations'; | |||
import { mockGitlabProject } from '../../../../helpers/mocks/alm-integrations'; | |||
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||
import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; | |||
@@ -33,8 +28,6 @@ import { AlmKeys } from '../../../../types/alm-settings'; | |||
import GitlabProjectCreate from '../GitlabProjectCreate'; | |||
jest.mock('../../../../api/alm-integrations', () => ({ | |||
checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue({ status: true }), | |||
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null), | |||
getGitlabProjects: jest.fn().mockRejectedValue('error'), | |||
importGitlabProject: jest.fn().mockRejectedValue('error') | |||
})); | |||
@@ -47,84 +40,7 @@ it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should correctly check PAT on mount', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalledWith(almSettingKey); | |||
}); | |||
it('should correctly check PAT when settings are added after mount', async () => { | |||
const wrapper = shallowRender({ settings: [] }); | |||
await waitAndUpdate(wrapper); | |||
wrapper.setProps({ | |||
settings: [mockAlmSettingsInstance({ alm: AlmKeys.GitLab, key: 'otherKey' })] | |||
}); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalledWith('otherKey'); | |||
}); | |||
it('should correctly handle a valid PAT', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: true }); | |||
(getGitlabProjects as jest.Mock).mockResolvedValueOnce({ | |||
projects: [mockGitlabProject()], | |||
projectsPaging: { | |||
pageIndex: 1, | |||
pageSize: 10, | |||
total: 1 | |||
} | |||
}); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().tokenIsValid).toBe(true); | |||
}); | |||
it('should correctly handle an invalid PAT', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: false }); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().tokenIsValid).toBe(false); | |||
}); | |||
describe('setting a new PAT', () => { | |||
const routerReplace = jest.fn(); | |||
const wrapper = shallowRender({ router: mockRouter({ replace: routerReplace }) }); | |||
beforeEach(() => { | |||
jest.clearAllMocks(); | |||
}); | |||
it('should correctly handle it if invalid', async () => { | |||
const error = 'error message'; | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: false, error }); | |||
wrapper.instance().handlePersonalAccessTokenCreate('invalidtoken'); | |||
expect(setAlmPersonalAccessToken).toBeCalledWith(almSettingKey, 'invalidtoken'); | |||
expect(wrapper.state().submittingToken).toBe(true); | |||
await waitAndUpdate(wrapper); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalled(); | |||
expect(wrapper.state().submittingToken).toBe(false); | |||
expect(wrapper.state().tokenValidationErrorMessage).toBe(error); | |||
}); | |||
it('should correctly handle it if valid', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: true }); | |||
wrapper.instance().handlePersonalAccessTokenCreate('validtoken'); | |||
expect(setAlmPersonalAccessToken).toBeCalledWith(almSettingKey, 'validtoken'); | |||
expect(wrapper.state().submittingToken).toBe(true); | |||
await waitAndUpdate(wrapper); | |||
expect(checkPersonalAccessTokenIsValid).toBeCalled(); | |||
expect(wrapper.state().submittingToken).toBe(false); | |||
expect(wrapper.state().tokenValidationErrorMessage).toBeUndefined(); | |||
expect(routerReplace).toBeCalled(); | |||
}); | |||
}); | |||
it('should fetch more projects and preserve search', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: true }); | |||
const projects = [ | |||
mockGitlabProject({ id: '1' }), | |||
mockGitlabProject({ id: '2' }), | |||
@@ -153,7 +69,7 @@ it('should fetch more projects and preserve search', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
await wrapper.instance().handlePersonalAccessTokenCreated(); | |||
wrapper.setState({ searchQuery: 'query' }); | |||
wrapper.instance().handleLoadMore(); | |||
@@ -167,8 +83,6 @@ it('should fetch more projects and preserve search', async () => { | |||
}); | |||
it('should search for projects', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: true }); | |||
const projects = [ | |||
mockGitlabProject({ id: '1' }), | |||
mockGitlabProject({ id: '2' }), | |||
@@ -197,11 +111,10 @@ it('should search for projects', async () => { | |||
const query = 'query'; | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
await wrapper.instance().handlePersonalAccessTokenCreated(); | |||
wrapper.instance().handleSearch(query); | |||
expect(wrapper.state().searching).toBe(true); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().searching).toBe(false); | |||
expect(wrapper.state().searchQuery).toBe(query); | |||
@@ -211,8 +124,6 @@ it('should search for projects', async () => { | |||
}); | |||
it('should import', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: true }); | |||
const projects = [mockGitlabProject({ id: '1' }), mockGitlabProject({ id: '2' })]; | |||
(getGitlabProjects as jest.Mock).mockResolvedValueOnce({ | |||
projects, | |||
@@ -231,7 +142,7 @@ it('should import', async () => { | |||
const onProjectCreate = jest.fn(); | |||
const wrapper = shallowRender({ onProjectCreate }); | |||
await waitAndUpdate(wrapper); | |||
await wrapper.instance().handlePersonalAccessTokenCreated(); | |||
wrapper.instance().handleImport(projects[1].id); | |||
expect(wrapper.state().importingGitlabProjectId).toBe(projects[1].id); | |||
@@ -245,17 +156,13 @@ it('should import', async () => { | |||
it('should do nothing with missing settings', async () => { | |||
const wrapper = shallowRender({ settings: [] }); | |||
await waitAndUpdate(wrapper); | |||
wrapper.instance().handleLoadMore(); | |||
wrapper.instance().handleSearch('whatever'); | |||
wrapper.instance().handlePersonalAccessTokenCreate('token'); | |||
wrapper.instance().handleImport('gitlab project id'); | |||
await wrapper.instance().handleLoadMore(); | |||
await wrapper.instance().handleSearch('whatever'); | |||
await wrapper.instance().handlePersonalAccessTokenCreated(); | |||
await wrapper.instance().handleImport('gitlab project id'); | |||
expect(checkPersonalAccessTokenIsValid).not.toHaveBeenCalled(); | |||
expect(getGitlabProjects).not.toHaveBeenCalled(); | |||
expect(importGitlabProject).not.toHaveBeenCalled(); | |||
expect(setAlmPersonalAccessToken).not.toHaveBeenCalled(); | |||
}); | |||
it('should handle errors when fetching projects', async () => { | |||
@@ -263,8 +170,10 @@ it('should handle errors when fetching projects', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
await wrapper.instance().handlePersonalAccessTokenCreated(); | |||
expect(wrapper.state().tokenIsValid).toBe(false); | |||
expect(wrapper.state().resetPat).toBe(true); | |||
expect(wrapper.state().showPersonalAccessTokenForm).toBe(true); | |||
}); | |||
it('should handle errors when importing a project', async () => { | |||
@@ -279,14 +188,12 @@ it('should handle errors when importing a project', async () => { | |||
}); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().tokenIsValid).toBe(true); | |||
await wrapper.instance().handlePersonalAccessTokenCreated(); | |||
await wrapper.instance().handleImport('gitlabId'); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().tokenIsValid).toBe(false); | |||
expect(wrapper.state().showPersonalAccessTokenForm).toBe(true); | |||
}); | |||
function shallowRender(props: Partial<GitlabProjectCreate['props']> = {}) { |
@@ -35,9 +35,6 @@ it('should render correctly', () => { | |||
expect(shallowRender({ showPersonalAccessTokenForm: false })).toMatchSnapshot( | |||
'project selection form' | |||
); | |||
expect(shallowRender({ tokenValidationErrorMessage: 'error' })).toMatchSnapshot( | |||
'pat validation error' | |||
); | |||
}); | |||
function shallowRender(props: Partial<GitlabProjectCreateRendererProps> = {}) { | |||
@@ -48,14 +45,14 @@ function shallowRender(props: Partial<GitlabProjectCreateRendererProps> = {}) { | |||
loadingMore={false} | |||
onImport={jest.fn()} | |||
onLoadMore={jest.fn()} | |||
onPersonalAccessTokenCreate={jest.fn()} | |||
onPersonalAccessTokenCreated={jest.fn()} | |||
onSearch={jest.fn()} | |||
projects={undefined} | |||
projectsPaging={{ pageIndex: 1, pageSize: 30, total: 0 }} | |||
searching={false} | |||
searchQuery="" | |||
resetPat={false} | |||
showPersonalAccessTokenForm={true} | |||
submittingToken={false} | |||
settings={mockAlmSettingsInstance({ alm: AlmKeys.GitLab })} | |||
{...props} | |||
/> |
@@ -20,37 +20,59 @@ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import { change, submit } from 'sonar-ui-common/helpers/testUtils'; | |||
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||
import { change, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { | |||
checkPersonalAccessTokenIsValid, | |||
setAlmPersonalAccessToken | |||
} from '../../../../api/alm-integrations'; | |||
import { | |||
mockAlmSettingsInstance, | |||
mockBitbucketCloudAlmSettingsInstance | |||
} from '../../../../helpers/mocks/alm-settings'; | |||
import { AlmKeys } from '../../../../types/alm-settings'; | |||
import PersonalAccessTokenForm, { PersonalAccessTokenFormProps } from '../PersonalAccessTokenForm'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('bitbucket'); | |||
expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting'); | |||
expect(shallowRender({ validationFailed: true })).toMatchSnapshot('validation failed'); | |||
expect( | |||
shallowRender({ validationFailed: true, validationErrorMessage: 'error' }) | |||
).toMatchSnapshot('validation failed, custom error message'); | |||
expect( | |||
shallowRender({ | |||
almSetting: mockAlmSettingsInstance({ alm: AlmKeys.GitLab, url: 'https://gitlab.com/api/v4' }) | |||
}) | |||
).toMatchSnapshot('gitlab'); | |||
expect( | |||
shallowRender({ | |||
almSetting: mockAlmSettingsInstance({ | |||
alm: AlmKeys.GitLab, | |||
url: 'https://gitlabapi.unexpectedurl.org' | |||
}) | |||
import PersonalAccessTokenForm from '../PersonalAccessTokenForm'; | |||
jest.mock('../../../../api/alm-integrations', () => ({ | |||
checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue({ status: true }), | |||
setAlmPersonalAccessToken: jest.fn().mockResolvedValue({}) | |||
})); | |||
it('should render correctly', async () => { | |||
expect(shallowRender()).toMatchSnapshot('no token needed'); | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: false }); | |||
let wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot('bitbucket'); | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: false }); | |||
wrapper = shallowRender({ almSetting: mockBitbucketCloudAlmSettingsInstance() }); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot('bitbucket cloud'); | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: false }); | |||
wrapper = shallowRender({ | |||
almSetting: mockAlmSettingsInstance({ alm: AlmKeys.GitLab, url: 'https://gitlab.com/api/v4' }) | |||
}); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot('gitlab'); | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce({ status: false }); | |||
wrapper = shallowRender({ | |||
almSetting: mockAlmSettingsInstance({ | |||
alm: AlmKeys.GitLab, | |||
url: 'https://gitlabapi.unexpectedurl.org' | |||
}) | |||
).toMatchSnapshot('gitlab with non-standard api path'); | |||
}); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot('gitlab with non-standard api path'); | |||
}); | |||
it('should correctly handle form interactions', () => { | |||
const onPersonalAccessTokenCreate = jest.fn(); | |||
const wrapper = shallowRender({ onPersonalAccessTokenCreate }); | |||
it('should correctly handle form interactions', async () => { | |||
const onPersonalAccessTokenCreated = jest.fn(); | |||
const wrapper = shallowRender({ onPersonalAccessTokenCreated }); | |||
await waitAndUpdate(wrapper); | |||
// Submit button disabled by default. | |||
expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); | |||
@@ -60,25 +82,62 @@ it('should correctly handle form interactions', () => { | |||
// Expect correct calls to be made when submitting. | |||
submit(wrapper.find('form')); | |||
expect(onPersonalAccessTokenCreate).toBeCalled(); | |||
expect(onPersonalAccessTokenCreated).toBeCalled(); | |||
expect(setAlmPersonalAccessToken).toBeCalledWith('key', 'token', undefined); | |||
}); | |||
it('should correctly handle form for bitbucket interactions', async () => { | |||
const onPersonalAccessTokenCreated = jest.fn(); | |||
const wrapper = shallowRender({ | |||
almSetting: mockBitbucketCloudAlmSettingsInstance(), | |||
onPersonalAccessTokenCreated | |||
}); | |||
await waitAndUpdate(wrapper); | |||
// Submit button disabled by default. | |||
expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); | |||
// If validation fails, we toggle the submitting flag and call useEffect() | |||
// to set the `touched` flag to false again. Trigger a re-render, and mock | |||
// useEffect(). This should de-activate the submit button again. | |||
jest.spyOn(React, 'useEffect').mockImplementationOnce(f => f()); | |||
wrapper.setProps({ submitting: false }); | |||
change(wrapper.find('#personal_access_token'), 'token'); | |||
expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); | |||
// Submit button enabled if there's a value. | |||
change(wrapper.find('#username'), 'username'); | |||
expect(wrapper.find(SubmitButton).prop('disabled')).toBe(false); | |||
// Expect correct calls to be made when submitting. | |||
submit(wrapper.find('form')); | |||
expect(onPersonalAccessTokenCreated).toBeCalled(); | |||
expect(setAlmPersonalAccessToken).toBeCalledWith('key', 'token', 'username'); | |||
}); | |||
it('should show error when issue', async () => { | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockRejectedValueOnce({}); | |||
const wrapper = shallowRender({ | |||
almSetting: mockBitbucketCloudAlmSettingsInstance() | |||
}); | |||
await waitAndUpdate(wrapper); | |||
(checkPersonalAccessTokenIsValid as jest.Mock).mockRejectedValueOnce({}); | |||
change(wrapper.find('#personal_access_token'), 'token'); | |||
change(wrapper.find('#username'), 'username'); | |||
// Expect correct calls to be made when submitting. | |||
submit(wrapper.find('form')); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot('issue submitting token'); | |||
}); | |||
function shallowRender(props: Partial<PersonalAccessTokenFormProps> = {}) { | |||
return shallow<PersonalAccessTokenFormProps>( | |||
function shallowRender(props: Partial<PersonalAccessTokenForm['props']> = {}) { | |||
return shallow<PersonalAccessTokenForm>( | |||
<PersonalAccessTokenForm | |||
almSetting={mockAlmSettingsInstance({ | |||
alm: AlmKeys.BitbucketServer, | |||
url: 'http://www.example.com' | |||
})} | |||
onPersonalAccessTokenCreate={jest.fn()} | |||
validationFailed={false} | |||
onPersonalAccessTokenCreated={jest.fn()} | |||
resetPat={false} | |||
{...props} | |||
/> | |||
); |
@@ -0,0 +1,33 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`Should render correctly 1`] = ` | |||
<BitbucketCloudProjectCreateRenderer | |||
canAdmin={true} | |||
loading={false} | |||
onPersonalAccessTokenCreated={[Function]} | |||
resetPat={false} | |||
settings={ | |||
Object { | |||
"alm": "bitbucketcloud", | |||
"key": "key", | |||
} | |||
} | |||
showPersonalAccessTokenForm={true} | |||
/> | |||
`; | |||
exports[`Should render correctly: Need App password 1`] = ` | |||
<BitbucketCloudProjectCreateRenderer | |||
canAdmin={true} | |||
loading={false} | |||
onPersonalAccessTokenCreated={[Function]} | |||
resetPat={false} | |||
settings={ | |||
Object { | |||
"alm": "bitbucketcloud", | |||
"key": "key", | |||
} | |||
} | |||
showPersonalAccessTokenForm={true} | |||
/> | |||
`; |
@@ -0,0 +1,101 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`Should render correctly 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.bitbucketcloud.title | |||
</span> | |||
} | |||
/> | |||
<p> | |||
Placeholder for next step | |||
</p> | |||
</Fragment> | |||
`; | |||
exports[`Should render correctly: Loading... 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.bitbucketcloud.title | |||
</span> | |||
} | |||
/> | |||
<i | |||
className="spinner" | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`Should render correctly: Need App password 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.bitbucketcloud.title | |||
</span> | |||
} | |||
/> | |||
<PersonalAccessTokenForm | |||
almSetting={ | |||
Object { | |||
"alm": "bitbucketcloud", | |||
"key": "key", | |||
} | |||
} | |||
onPersonalAccessTokenCreated={[MockFunction]} | |||
resetPat={false} | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`Should render correctly: Wrong config 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
onboarding.create_project.bitbucketcloud.title | |||
</span> | |||
} | |||
/> | |||
<WrongBindingCountAlert | |||
alm="bitbucketcloud" | |||
canAdmin={false} | |||
/> | |||
</Fragment> | |||
`; |
@@ -10,14 +10,66 @@ exports[`should render correctly 1`] = ` | |||
} | |||
canAdmin={false} | |||
importing={false} | |||
loading={true} | |||
loading={false} | |||
onImportRepository={[Function]} | |||
onPersonalAccessTokenCreate={[Function]} | |||
onPersonalAccessTokenCreated={[Function]} | |||
onProjectCreate={[MockFunction]} | |||
onSearch={[Function]} | |||
onSelectRepository={[Function]} | |||
resetPat={false} | |||
searching={false} | |||
showPersonalAccessTokenForm={true} | |||
/> | |||
`; | |||
exports[`should render correctly: No repository 1`] = ` | |||
<BitbucketProjectCreateRenderer | |||
bitbucketSetting={ | |||
Object { | |||
"alm": "bitbucket", | |||
"key": "foo", | |||
} | |||
} | |||
canAdmin={false} | |||
importing={false} | |||
loading={false} | |||
onImportRepository={[Function]} | |||
onPersonalAccessTokenCreated={[Function]} | |||
onProjectCreate={[MockFunction]} | |||
onSearch={[Function]} | |||
onSelectRepository={[Function]} | |||
projects={ | |||
Array [ | |||
Object { | |||
"id": 1, | |||
"key": "project1", | |||
"name": "Project 1", | |||
}, | |||
Object { | |||
"id": 2, | |||
"key": "project2", | |||
"name": "Project", | |||
}, | |||
] | |||
} | |||
resetPat={false} | |||
searching={false} | |||
showPersonalAccessTokenForm={false} | |||
/> | |||
`; | |||
exports[`should render correctly: No setting 1`] = ` | |||
<BitbucketProjectCreateRenderer | |||
canAdmin={false} | |||
importing={false} | |||
loading={false} | |||
onImportRepository={[Function]} | |||
onPersonalAccessTokenCreated={[Function]} | |||
onProjectCreate={[MockFunction]} | |||
onSearch={[Function]} | |||
onSelectRepository={[Function]} | |||
resetPat={false} | |||
searching={false} | |||
showPersonalAccessTokenForm={true} | |||
tokenValidationFailed={false} | |||
/> | |||
`; |
@@ -281,8 +281,8 @@ exports[`should render correctly: pat form 1`] = ` | |||
"key": "key", | |||
} | |||
} | |||
onPersonalAccessTokenCreate={[MockFunction]} | |||
validationFailed={false} | |||
onPersonalAccessTokenCreated={[MockFunction]} | |||
resetPat={false} | |||
/> | |||
</Fragment> | |||
`; |
@@ -115,6 +115,19 @@ exports[`should render correctly if the BBS method is selected 1`] = ` | |||
} | |||
} | |||
onProjectCreate={[Function]} | |||
router={ | |||
Object { | |||
"createHref": [MockFunction], | |||
"createPath": [MockFunction], | |||
"go": [MockFunction], | |||
"goBack": [MockFunction], | |||
"goForward": [MockFunction], | |||
"isActive": [MockFunction], | |||
"push": [MockFunction], | |||
"replace": [MockFunction], | |||
"setRouteLeaveHook": [MockFunction], | |||
} | |||
} | |||
/> | |||
</div> | |||
</Fragment> |
@@ -3,11 +3,11 @@ | |||
exports[`should render correctly 1`] = ` | |||
<GitlabProjectCreateRenderer | |||
canAdmin={false} | |||
loading={true} | |||
loading={false} | |||
loadingMore={false} | |||
onImport={[Function]} | |||
onLoadMore={[Function]} | |||
onPersonalAccessTokenCreate={[Function]} | |||
onPersonalAccessTokenCreated={[Function]} | |||
onSearch={[Function]} | |||
projectsPaging={ | |||
Object { | |||
@@ -16,6 +16,7 @@ exports[`should render correctly 1`] = ` | |||
"total": 0, | |||
} | |||
} | |||
resetPat={false} | |||
searchQuery="" | |||
searching={false} | |||
settings={ | |||
@@ -25,6 +26,5 @@ exports[`should render correctly 1`] = ` | |||
} | |||
} | |||
showPersonalAccessTokenForm={true} | |||
submittingToken={false} | |||
/> | |||
`; |
@@ -95,41 +95,8 @@ exports[`should render correctly: pat form 1`] = ` | |||
"key": "key", | |||
} | |||
} | |||
onPersonalAccessTokenCreate={[MockFunction]} | |||
submitting={false} | |||
validationFailed={false} | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: pat validation error 1`] = ` | |||
<Fragment> | |||
<CreateProjectPageHeader | |||
title={ | |||
<span | |||
className="text-middle" | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height="24" | |||
src="/images/alm/gitlab.svg" | |||
/> | |||
onboarding.create_project.gitlab.title | |||
</span> | |||
} | |||
/> | |||
<PersonalAccessTokenForm | |||
almSetting={ | |||
Object { | |||
"alm": "gitlab", | |||
"key": "key", | |||
} | |||
} | |||
onPersonalAccessTokenCreate={[MockFunction]} | |||
submitting={false} | |||
validationErrorMessage="error" | |||
validationFailed={true} | |||
onPersonalAccessTokenCreated={[MockFunction]} | |||
resetPat={false} | |||
/> | |||
</Fragment> | |||
`; |
@@ -19,7 +19,8 @@ exports[`should render correctly: bitbucket 1`] = ` | |||
onboarding.create_project.pat_form.help.bitbucket | |||
</p> | |||
<ValidationInput | |||
id="personal_access_token" | |||
error="onboarding.create_project.pat_incorrect.bitbucket" | |||
id="personal_access_token_validation" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.enter_pat" | |||
@@ -27,23 +28,31 @@ exports[`should render correctly: bitbucket 1`] = ` | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-super-large" | |||
className="input-super-large is-invalid" | |||
id="personal_access_token" | |||
minLength={1} | |||
name="personal_access_token" | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
error="onboarding.create_project.pat_incorrect.bitbucket" | |||
id="personal_access_token_submit" | |||
isInvalid={true} | |||
isValid={false} | |||
label={null} | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</form> | |||
<Alert | |||
className="big-spacer-left width-50" | |||
@@ -120,7 +129,7 @@ exports[`should render correctly: bitbucket 1`] = ` | |||
</div> | |||
`; | |||
exports[`should render correctly: gitlab 1`] = ` | |||
exports[`should render correctly: bitbucket cloud 1`] = ` | |||
<div | |||
className="display-flex-start" | |||
> | |||
@@ -131,39 +140,65 @@ exports[`should render correctly: gitlab 1`] = ` | |||
<h2 | |||
className="big" | |||
> | |||
onboarding.create_project.pat_form.title.gitlab | |||
onboarding.create_project.pat_form.title.bitbucketcloud | |||
</h2> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_form.help.gitlab | |||
onboarding.create_project.pat_form.help.bitbucketcloud | |||
</p> | |||
<ValidationInput | |||
id="personal_access_token" | |||
id="enter_username_validation" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.enter_pat" | |||
label="onboarding.create_project.enter_username" | |||
required={true} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-super-large" | |||
className="input-super-large is-invalid" | |||
id="username" | |||
minLength={1} | |||
name="username" | |||
onChange={[Function]} | |||
type="text" | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
error="onboarding.create_project.pat_incorrect.bitbucketcloud" | |||
id="personal_access_token_validation" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.enter_pat.bitbucketcloud" | |||
required={true} | |||
> | |||
<input | |||
autoFocus={false} | |||
className="input-super-large is-invalid" | |||
id="personal_access_token" | |||
minLength={1} | |||
name="personal_access_token" | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
error="onboarding.create_project.pat_incorrect.bitbucketcloud" | |||
id="personal_access_token_submit" | |||
isInvalid={true} | |||
isValid={false} | |||
label={null} | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</form> | |||
<Alert | |||
className="big-spacer-left width-50" | |||
@@ -171,17 +206,17 @@ exports[`should render correctly: gitlab 1`] = ` | |||
variant="info" | |||
> | |||
<h3> | |||
onboarding.create_project.pat_help.title | |||
onboarding.create_project.pat_help.bitbucketcloud.title | |||
</h3> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
<FormattedMessage | |||
defaultMessage="onboarding.create_project.pat_help.instructions" | |||
defaultMessage="onboarding.create_project.pat_help.bitbucketcloud.instructions" | |||
id="onboarding.create_project.pat_help.instructions" | |||
values={ | |||
Object { | |||
"alm": "onboarding.alm.gitlab", | |||
"alm": "onboarding.alm.bitbucketcloud", | |||
} | |||
} | |||
/> | |||
@@ -193,35 +228,41 @@ exports[`should render correctly: gitlab 1`] = ` | |||
alt="" | |||
className="spacer-right" | |||
height="16" | |||
src="/images/alm/gitlab.svg" | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
<a | |||
href="https://gitlab.com/profile/personal_access_tokens" | |||
href="https://bitbucket.org/account/settings/app-passwords/new" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
onboarding.create_project.pat_help.link | |||
onboarding.create_project.pat_help.bitbucketcloud.link | |||
</a> | |||
</div> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_help.instructions2.gitlab | |||
onboarding.create_project.pat_help.instructions2.bitbucketcloud | |||
</p> | |||
<ul> | |||
<li | |||
className="spacer-bottom" | |||
> | |||
<strong> | |||
onboarding.create_project.pat_help.gitlab.read_api_permission | |||
</strong> | |||
<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: gitlab with non-standard api path 1`] = ` | |||
exports[`should render correctly: gitlab 1`] = ` | |||
<div | |||
className="display-flex-start" | |||
> | |||
@@ -240,7 +281,8 @@ exports[`should render correctly: gitlab with non-standard api path 1`] = ` | |||
onboarding.create_project.pat_form.help.gitlab | |||
</p> | |||
<ValidationInput | |||
id="personal_access_token" | |||
error="onboarding.create_project.pat_incorrect.gitlab" | |||
id="personal_access_token_validation" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.enter_pat" | |||
@@ -248,23 +290,31 @@ exports[`should render correctly: gitlab with non-standard api path 1`] = ` | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-super-large" | |||
className="input-super-large is-invalid" | |||
id="personal_access_token" | |||
minLength={1} | |||
name="personal_access_token" | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
error="onboarding.create_project.pat_incorrect.gitlab" | |||
id="personal_access_token_submit" | |||
isInvalid={true} | |||
isValid={false} | |||
label={null} | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</form> | |||
<Alert | |||
className="big-spacer-left width-50" | |||
@@ -297,7 +347,7 @@ exports[`should render correctly: gitlab with non-standard api path 1`] = ` | |||
src="/images/alm/gitlab.svg" | |||
/> | |||
<a | |||
href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token" | |||
href="https://gitlab.com/profile/personal_access_tokens" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
@@ -322,7 +372,7 @@ exports[`should render correctly: gitlab with non-standard api path 1`] = ` | |||
</div> | |||
`; | |||
exports[`should render correctly: submitting 1`] = ` | |||
exports[`should render correctly: gitlab with non-standard api path 1`] = ` | |||
<div | |||
className="display-flex-start" | |||
> | |||
@@ -333,15 +383,16 @@ exports[`should render correctly: submitting 1`] = ` | |||
<h2 | |||
className="big" | |||
> | |||
onboarding.create_project.pat_form.title.bitbucket | |||
onboarding.create_project.pat_form.title.gitlab | |||
</h2> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_form.help.bitbucket | |||
onboarding.create_project.pat_form.help.gitlab | |||
</p> | |||
<ValidationInput | |||
id="personal_access_token" | |||
error="onboarding.create_project.pat_incorrect.gitlab" | |||
id="personal_access_token_validation" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.enter_pat" | |||
@@ -349,23 +400,31 @@ exports[`should render correctly: submitting 1`] = ` | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-super-large" | |||
className="input-super-large is-invalid" | |||
id="personal_access_token" | |||
minLength={1} | |||
name="personal_access_token" | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
error="onboarding.create_project.pat_incorrect.gitlab" | |||
id="personal_access_token_submit" | |||
isInvalid={true} | |||
isValid={false} | |||
label={null} | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={true} | |||
/> | |||
</form> | |||
<Alert | |||
className="big-spacer-left width-50" | |||
@@ -383,7 +442,7 @@ exports[`should render correctly: submitting 1`] = ` | |||
id="onboarding.create_project.pat_help.instructions" | |||
values={ | |||
Object { | |||
"alm": "onboarding.alm.bitbucket", | |||
"alm": "onboarding.alm.gitlab", | |||
} | |||
} | |||
/> | |||
@@ -395,10 +454,10 @@ exports[`should render correctly: submitting 1`] = ` | |||
alt="" | |||
className="spacer-right" | |||
height="16" | |||
src="/images/alm/bitbucket.svg" | |||
src="/images/alm/gitlab.svg" | |||
/> | |||
<a | |||
href="http://www.example.com/plugins/servlet/access-tokens/add" | |||
href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
@@ -408,41 +467,29 @@ exports[`should render correctly: submitting 1`] = ` | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_help.instructions2.bitbucket | |||
onboarding.create_project.pat_help.instructions2.gitlab | |||
</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 | |||
className="spacer-bottom" | |||
> | |||
<strong> | |||
onboarding.create_project.pat_help.gitlab.read_api_permission | |||
</strong> | |||
</li> | |||
</ul> | |||
</Alert> | |||
</div> | |||
`; | |||
exports[`should render correctly: validation failed 1`] = ` | |||
exports[`should render correctly: no token needed 1`] = ` | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={true} | |||
/> | |||
`; | |||
exports[`should show error when issue: issue submitting token 1`] = ` | |||
<div | |||
className="display-flex-start" | |||
> | |||
@@ -453,161 +500,66 @@ exports[`should render correctly: validation failed 1`] = ` | |||
<h2 | |||
className="big" | |||
> | |||
onboarding.create_project.pat_form.title.bitbucket | |||
onboarding.create_project.pat_form.title.bitbucketcloud | |||
</h2> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_form.help.bitbucket | |||
onboarding.create_project.pat_form.help.bitbucketcloud | |||
</p> | |||
<ValidationInput | |||
error="onboarding.create_project.pat_incorrect.bitbucket" | |||
id="personal_access_token" | |||
isInvalid={true} | |||
id="enter_username_validation" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.enter_pat" | |||
label="onboarding.create_project.enter_username" | |||
required={true} | |||
> | |||
<input | |||
autoFocus={true} | |||
className="input-super-large is-invalid" | |||
id="personal_access_token" | |||
id="username" | |||
minLength={1} | |||
name="personal_access_token" | |||
name="username" | |||
onChange={[Function]} | |||
type="text" | |||
value="username" | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</form> | |||
<Alert | |||
className="big-spacer-left width-50" | |||
display="block" | |||
variant="info" | |||
> | |||
<h3> | |||
onboarding.create_project.pat_help.title | |||
</h3> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
<FormattedMessage | |||
defaultMessage="onboarding.create_project.pat_help.instructions" | |||
id="onboarding.create_project.pat_help.instructions" | |||
values={ | |||
Object { | |||
"alm": "onboarding.alm.bitbucket", | |||
} | |||
} | |||
/> | |||
</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.instructions2.bitbucket | |||
</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: validation failed, custom error message 1`] = ` | |||
<div | |||
className="display-flex-start" | |||
> | |||
<form | |||
className="width-50" | |||
onSubmit={[Function]} | |||
> | |||
<h2 | |||
className="big" | |||
> | |||
onboarding.create_project.pat_form.title.bitbucket | |||
</h2> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_form.help.bitbucket | |||
</p> | |||
<ValidationInput | |||
error="error" | |||
id="personal_access_token" | |||
isInvalid={true} | |||
error="default_error_message" | |||
id="personal_access_token_validation" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_project.enter_pat" | |||
label="onboarding.create_project.enter_pat.bitbucketcloud" | |||
required={true} | |||
> | |||
<input | |||
autoFocus={true} | |||
autoFocus={false} | |||
className="input-super-large is-invalid" | |||
id="personal_access_token" | |||
minLength={1} | |||
name="personal_access_token" | |||
onChange={[Function]} | |||
type="text" | |||
value="token" | |||
/> | |||
</ValidationInput> | |||
<ValidationInput | |||
error="default_error_message" | |||
id="personal_access_token_submit" | |||
isInvalid={true} | |||
isValid={false} | |||
label={null} | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</ValidationInput> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
/> | |||
</form> | |||
<Alert | |||
className="big-spacer-left width-50" | |||
@@ -615,17 +567,17 @@ exports[`should render correctly: validation failed, custom error message 1`] = | |||
variant="info" | |||
> | |||
<h3> | |||
onboarding.create_project.pat_help.title | |||
onboarding.create_project.pat_help.bitbucketcloud.title | |||
</h3> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
<FormattedMessage | |||
defaultMessage="onboarding.create_project.pat_help.instructions" | |||
defaultMessage="onboarding.create_project.pat_help.bitbucketcloud.instructions" | |||
id="onboarding.create_project.pat_help.instructions" | |||
values={ | |||
Object { | |||
"alm": "onboarding.alm.bitbucket", | |||
"alm": "onboarding.alm.bitbucketcloud", | |||
} | |||
} | |||
/> | |||
@@ -640,32 +592,19 @@ exports[`should render correctly: validation failed, custom error message 1`] = | |||
src="/images/alm/bitbucket.svg" | |||
/> | |||
<a | |||
href="http://www.example.com/plugins/servlet/access-tokens/add" | |||
href="https://bitbucket.org/account/settings/app-passwords/new" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
onboarding.create_project.pat_help.link | |||
onboarding.create_project.pat_help.bitbucketcloud.link | |||
</a> | |||
</div> | |||
<p | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
onboarding.create_project.pat_help.instructions2.bitbucket | |||
onboarding.create_project.pat_help.instructions2.bitbucketcloud | |||
</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" |
@@ -21,6 +21,7 @@ export enum CreateProjectModes { | |||
Manual = 'manual', | |||
AzureDevOps = 'azure', | |||
BitbucketServer = 'bitbucket', | |||
BitbucketCloud = 'bitbucketcloud', | |||
GitHub = 'github', | |||
GitLab = 'gitlab' | |||
} |
@@ -44,6 +44,16 @@ export function mockAlmSettingsInstance( | |||
}; | |||
} | |||
export function mockBitbucketCloudAlmSettingsInstance( | |||
overrides: Partial<AlmSettingsInstance> = {} | |||
): AlmSettingsInstance { | |||
return { | |||
alm: AlmKeys.BitbucketCloud, | |||
key: 'key', | |||
...overrides | |||
}; | |||
} | |||
export function mockAzureBindingDefinition( | |||
overrides: Partial<AzureBindingDefinition> = {} | |||
): AzureBindingDefinition { |
@@ -3200,6 +3200,7 @@ footer.web_api=Web API | |||
#------------------------------------------------------------------------------ | |||
onboarding.alm.azure=Azure DevOps | |||
onboarding.alm.bitbucket=Bitbucket Server | |||
onboarding.alm.bitbucketcloud=Bitbucket Cloud | |||
onboarding.alm.gitlab=GitLab | |||
onboarding.project_analysis.header=Analyze your project | |||
@@ -3243,9 +3244,11 @@ onboarding.create_application.key.description=If specified, this value is used a | |||
onboarding.create_project.pat_form.title.azure=Allow SonarQube to access and list your Azure DevOps repositories | |||
onboarding.create_project.pat_form.title.bitbucket=Grant access to your repositories | |||
onboarding.create_project.pat_form.title.bitbucketcloud=Grant access to your repositories | |||
onboarding.create_project.pat_form.title.gitlab=Grant access to your projects | |||
onboarding.create_project.pat_form.help.azure=SonarQube needs a personal access token to access and list your repositories from Azure DevOps. | |||
onboarding.create_project.pat_form.help.bitbucket=SonarQube needs a personal access token to access and list your repositories from Bitbucket Server. | |||
onboarding.create_project.pat_form.help.bitbucketcloud=SonarQube needs an app password to access and list your repositories from Bitbucket Cloud. | |||
onboarding.create_project.pat_form.help.gitlab=SonarQube needs a personal access token to access and list your projects from GitLab. | |||
onboarding.create_project.pat_form.pat_required=Please enter a personal access token | |||
onboarding.create_project.pat_form.list_repositories=List repositories | |||
@@ -3262,17 +3265,26 @@ onboarding.create_project.zero_alm_instances.gitlab=You must first configure a G | |||
onboarding.create_project.wrong_binding_count=You must have exactly 1 {alm} 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.wrong_binding_count.admin=You must have exactly 1 {alm} 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.enter_pat.bitbucketcloud=Enter your app password | |||
onboarding.create_project.enter_username=Enter your Bitbucket username | |||
onboarding.create_project.pat_incorrect.azure=Your personal access couldn't be validated. | |||
onboarding.create_project.pat_incorrect.bitbucket=Your personal access couldn't be validated. | |||
onboarding.create_project.pat_incorrect.bitbucketcloud=Your app password couldn't be validated. | |||
onboarding.create_project.pat_incorrect.gitlab=Your personal access couldn't be validated. Please make sure it has the right scope and that it is not expired. | |||
onboarding.create_project.pat_help.title=How to create a personal access token? | |||
onboarding.create_project.pat_help.bitbucketcloud.title=How to create an app password? | |||
onboarding.create_project.pat_help.instructions.azure=Create and provide an Azure DevOps {link}. You need to select the {scope} scope so we can display a list of your repositories which are available for analysis. | |||
onboarding.create_project.pat_help.instructions.link.azure=personal access token | |||
onboarding.create_project.pat_help.instructions=Click the following link to generate a token in {alm}, and copy-paste it into the personal access token field. | |||
onboarding.create_project.pat_help.bitbucketcloud.instructions=Click the following link to generate an app password, and copy-paste it into the app password field. | |||
onboarding.create_project.pat_help.instructions2.bitbucket=Set a name, for example "SonarQube", and select the following permissions: | |||
onboarding.create_project.pat_help.instructions2.bitbucketcloud=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.bitbucketcloud.link=Add app password | |||
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 | |||
@@ -3292,6 +3304,7 @@ onboarding.create_project.azure.title=Which Azure DevOps repository do you want | |||
onboarding.create_project.azure.no_projects=No projects could be fetched from Azure DevOps. Contact your system administrator, or {link}. | |||
onboarding.create_project.azure.no_repositories=Could not fetch repositories for this project. Contact your system administrator, or {link}. | |||
onboarding.create_project.azure.no_results=No repositories match your search query. | |||
onboarding.create_project.bitbucketcloud.title=Which Bitbucket Cloud repository do you want to set up? | |||
onboarding.create_project.github.title=Which GitHub repository do you want to set up? | |||
onboarding.create_project.github.choose_organization=Choose organization | |||
onboarding.create_project.github.warning.title=Could not connect to GitHub |