diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2020-07-22 10:57:38 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2020-08-17 20:06:22 +0000 |
commit | c9460063e843eef0c94a442c3326c4d24d1a921b (patch) | |
tree | f7bdbc7734f6f085a9af3c8069d49363220268db | |
parent | 30d1c1a1e306b6e3ee0fe8d59df6f4f8d5640172 (diff) | |
download | sonarqube-c9460063e843eef0c94a442c3326c4d24d1a921b.tar.gz sonarqube-c9460063e843eef0c94a442c3326c4d24d1a921b.zip |
SONAR-13628 Add PAT form for GitLab onboarding
23 files changed, 1201 insertions, 144 deletions
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx index 0b58cdc9491..65eb8c75bfe 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx @@ -47,7 +47,7 @@ interface State { /* * ALMs for which the import feature has been implemented */ -const IMPORT_COMPATIBLE_ALMS = [AlmKeys.Bitbucket, AlmKeys.GitHub]; +const IMPORT_COMPATIBLE_ALMS = [AlmKeys.Bitbucket, AlmKeys.GitHub, AlmKeys.GitLab]; export class GlobalNavPlus extends React.PureComponent<Props, State> { mounted = false; diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx index 878a11b609c..88eaf370202 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx @@ -18,10 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router'; import { Button } from 'sonar-ui-common/components/controls/buttons'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; @@ -30,11 +27,11 @@ import { BitbucketProjectRepositories, BitbucketRepository } from '../../../types/alm-integration'; -import { AlmSettingsInstance } from '../../../types/alm-settings'; -import { ALM_INTEGRATION } from '../../settings/components/AdditionalCategoryKeys'; +import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; import BitbucketImportRepositoryForm from './BitbucketImportRepositoryForm'; -import BitbucketPersonalAccessTokenForm from './BitbucketPersonalAccessTokenForm'; import CreateProjectPageHeader from './CreateProjectPageHeader'; +import PersonalAccessTokenForm from './PersonalAccessTokenForm'; +import WrongBindingCountAlert from './WrongBindingCountAlert'; export interface BitbucketProjectCreateRendererProps { bitbucketSetting?: AlmSettingsInstance; @@ -104,34 +101,14 @@ export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCr {loading && <i className="spinner" />} {!loading && !bitbucketSetting && ( - <Alert variant="error"> - {canAdmin ? ( - <FormattedMessage - defaultMessage={translate('onboarding.create_project.no_bbs_binding.admin')} - id="onboarding.create_project.no_bbs_binding.admin" - values={{ - url: ( - <Link - to={{ - pathname: '/admin/settings', - query: { category: ALM_INTEGRATION } - }}> - {translate('settings.page')} - </Link> - ) - }} - /> - ) : ( - translate('onboarding.create_project.no_bbs_binding') - )} - </Alert> + <WrongBindingCountAlert alm={AlmKeys.Bitbucket} canAdmin={!!canAdmin} /> )} {!loading && bitbucketSetting && (showPersonalAccessTokenForm ? ( - <BitbucketPersonalAccessTokenForm - bitbucketSetting={bitbucketSetting} + <PersonalAccessTokenForm + almSetting={bitbucketSetting} onPersonalAccessTokenCreate={props.onPersonalAccessTokenCreate} submitting={submittingToken} validationFailed={tokenValidationFailed} diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx index 43229c38eef..4508e2762c2 100644 --- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx @@ -44,7 +44,7 @@ function renderAlmOption( return ( <button className={classNames( - 'button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs', + 'button button-huge big-spacer-left display-flex-column create-project-mode-type-alm', { disabled } )} disabled={disabled} @@ -114,6 +114,7 @@ export default function CreateProjectModeSelection(props: CreateProjectModeSelec {renderAlmOption(props, AlmKeys.Bitbucket, CreateProjectModes.BitbucketServer)} {renderAlmOption(props, AlmKeys.GitHub, CreateProjectModes.GitHub)} + {renderAlmOption(props, AlmKeys.GitLab, CreateProjectModes.GitLab)} </div> </> ); diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx index 3b4183beae4..5b79dd82e6b 100644 --- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx @@ -30,6 +30,7 @@ import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; import BitbucketProjectCreate from './BitbucketProjectCreate'; import CreateProjectModeSelection from './CreateProjectModeSelection'; import GitHubProjectCreate from './GitHubProjectCreate'; +import GitlabProjectCreate from './GitlabProjectCreate'; import ManualProjectCreate from './ManualProjectCreate'; import './style.css'; import { CreateProjectModes } from './types'; @@ -42,12 +43,13 @@ interface Props extends Pick<WithRouterProps, 'router' | 'location'> { interface State { bitbucketSettings: AlmSettingsInstance[]; githubSettings: AlmSettingsInstance[]; + gitlabSettings: AlmSettingsInstance[]; loading: boolean; } export class CreateProjectPage extends React.PureComponent<Props, State> { mounted = false; - state: State = { bitbucketSettings: [], githubSettings: [], loading: true }; + state: State = { bitbucketSettings: [], githubSettings: [], gitlabSettings: [], loading: true }; componentDidMount() { const { @@ -71,6 +73,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { this.setState({ bitbucketSettings: almSettings.filter(s => s.alm === AlmKeys.Bitbucket), githubSettings: almSettings.filter(s => s.alm === AlmKeys.GitHub), + gitlabSettings: almSettings.filter(s => s.alm === AlmKeys.GitLab), loading: false }); } @@ -102,7 +105,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { location, router } = this.props; - const { bitbucketSettings, githubSettings, loading } = this.state; + const { bitbucketSettings, githubSettings, gitlabSettings, loading } = this.state; switch (mode) { case CreateProjectModes.BitbucketServer: { @@ -128,6 +131,17 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { /> ); } + case CreateProjectModes.GitLab: { + return ( + <GitlabProjectCreate + canAdmin={!!canAdmin} + loadingBindings={loading} + location={location} + onProjectCreate={this.handleProjectCreate} + settings={gitlabSettings} + /> + ); + } case CreateProjectModes.Manual: { return <ManualProjectCreate onProjectCreate={this.handleProjectCreate} />; } @@ -136,7 +150,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { [AlmKeys.Azure]: 0, [AlmKeys.Bitbucket]: bitbucketSettings.length, [AlmKeys.GitHub]: githubSettings.length, - [AlmKeys.GitLab]: 0 + [AlmKeys.GitLab]: gitlabSettings.length }; return ( <CreateProjectModeSelection diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx new file mode 100644 index 00000000000..33d05358658 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx @@ -0,0 +1,144 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { WithRouterProps } from 'react-router'; +import { + checkPersonalAccessTokenIsValid, + setAlmPersonalAccessToken +} from '../../../api/alm-integrations'; +import { AlmSettingsInstance } from '../../../types/alm-settings'; +import GitlabProjectCreateRenderer from './GitlabProjectCreateRenderer'; + +interface Props extends Pick<WithRouterProps, 'location'> { + canAdmin: boolean; + loadingBindings: boolean; + onProjectCreate: (projectKeys: string[]) => void; + settings: AlmSettingsInstance[]; +} + +interface State { + loading: boolean; + submittingToken: boolean; + tokenIsValid: boolean; + tokenValidationFailed: boolean; + settings?: AlmSettingsInstance; +} + +export default class GitlabProjectCreate extends React.PureComponent<Props, State> { + mounted = false; + + constructor(props: Props) { + super(props); + this.state = { + loading: false, + tokenIsValid: false, + settings: props.settings.length === 1 ? props.settings[0] : undefined, + submittingToken: false, + tokenValidationFailed: false + }; + } + + componentDidMount() { + this.mounted = true; + this.fetchInitialData(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.settings.length === 0 && this.props.settings.length > 0) { + this.setState( + { settings: this.props.settings.length === 1 ? this.props.settings[0] : undefined }, + () => this.fetchInitialData() + ); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchInitialData = async () => { + this.setState({ loading: true }); + + const tokenIsValid = await this.checkPersonalAccessToken(); + + if (this.mounted) { + this.setState({ + tokenIsValid, + loading: false + }); + } + }; + + checkPersonalAccessToken = () => { + const { settings } = this.state; + + if (!settings) { + return Promise.resolve(false); + } + + return checkPersonalAccessTokenIsValid(settings.key).catch(() => false); + }; + + handlePersonalAccessTokenCreate = (token: string) => { + const { settings } = this.state; + + if (!settings || token.length < 1) { + return; + } + + this.setState({ submittingToken: true, tokenValidationFailed: false }); + setAlmPersonalAccessToken(settings.key, token) + .then(this.checkPersonalAccessToken) + .then(patIsValid => { + if (this.mounted) { + this.setState({ + submittingToken: false, + tokenIsValid: patIsValid, + tokenValidationFailed: !patIsValid + }); + if (patIsValid) { + this.fetchInitialData(); + } + } + }) + .catch(() => { + if (this.mounted) { + this.setState({ submittingToken: false }); + } + }); + }; + + render() { + const { canAdmin, loadingBindings, location } = this.props; + const { loading, tokenIsValid, settings, submittingToken, tokenValidationFailed } = this.state; + + return ( + <GitlabProjectCreateRenderer + settings={settings} + canAdmin={canAdmin} + loading={loading || loadingBindings} + onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate} + showPersonalAccessTokenForm={!tokenIsValid || Boolean(location.query.resetPat)} + submittingToken={submittingToken} + tokenValidationFailed={tokenValidationFailed} + /> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx new file mode 100644 index 00000000000..90f47b85d01 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +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 GitlabProjectCreateRendererProps { + canAdmin?: boolean; + loading: boolean; + onPersonalAccessTokenCreate: (pat: string) => void; + settings?: AlmSettingsInstance; + showPersonalAccessTokenForm?: boolean; + submittingToken?: boolean; + tokenValidationFailed: boolean; +} + +export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRendererProps) { + const { + canAdmin, + loading, + settings, + showPersonalAccessTokenForm, + submittingToken, + tokenValidationFailed + } = 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/gitlab.svg`} + /> + {translate('onboarding.create_project.gitlab.title')} + </span> + } + /> + + {loading && <i className="spinner" />} + + {!loading && !settings && ( + <WrongBindingCountAlert alm={AlmKeys.GitLab} canAdmin={!!canAdmin} /> + )} + + {!loading && + settings && + (showPersonalAccessTokenForm ? ( + <PersonalAccessTokenForm + almSetting={settings} + onPersonalAccessTokenCreate={props.onPersonalAccessTokenCreate} + submitting={submittingToken} + validationFailed={tokenValidationFailed} + /> + ) : ( + <div>Token is valid!</div> + ))} + </> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketPersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx index 4f6826be68a..2a0342a5d33 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketPersonalAccessTokenForm.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx @@ -26,20 +26,29 @@ import { Alert } from 'sonar-ui-common/components/ui/Alert'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; -import { AlmSettingsInstance } from '../../../types/alm-settings'; +import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; -export interface BitbucketPersonalAccessTokenFormProps { - bitbucketSetting: AlmSettingsInstance; +export interface PersonalAccessTokenFormProps { + almSetting: AlmSettingsInstance; onPersonalAccessTokenCreate: (token: string) => void; submitting?: boolean; validationFailed: boolean; } -export default function BitbucketPersonalAccessTokenForm( - props: BitbucketPersonalAccessTokenFormProps -) { +function getPatUrl(alm: AlmKeys, url: string) { + if (alm === AlmKeys.Bitbucket) { + return `${url.replace(/\/$/, '')}/plugins/servlet/access-tokens/add`; + } else { + // GitLab + return url.endsWith('/api/v4') + ? `${url.replace('/api/v4', '').replace(/\/$/, '')}/profile/personal_access_tokens` + : 'https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token'; + } +} + +export default function PersonalAccessTokenForm(props: PersonalAccessTokenFormProps) { const { - bitbucketSetting: { url }, + almSetting: { alm, url }, submitting = false, validationFailed } = props; @@ -59,9 +68,9 @@ export default function BitbucketPersonalAccessTokenForm( const value = new FormData(e.currentTarget).get('personal_access_token') as string; props.onPersonalAccessTokenCreate(value); }}> - <h2 className="big">{translate('onboarding.create_project.grant_access_to_bbs.title')}</h2> + <h2 className="big">{translate('onboarding.create_project.pat_form.title', alm)}</h2> <p className="big-spacer-top big-spacer-bottom"> - {translate('onboarding.create_project.grant_access_to_bbs.help')} + {translate('onboarding.create_project.pat_form.help', alm)} </p> <ValidationInput @@ -96,7 +105,11 @@ export default function BitbucketPersonalAccessTokenForm( <h3>{translate('onboarding.create_project.pat_help.title')}</h3> <p className="big-spacer-top big-spacer-bottom"> - {translate('onboarding.create_project.pat_help.bbs_help_1')} + <FormattedMessage + id="onboarding.create_project.pat_help.instructions" + defaultMessage={translate('onboarding.create_project.pat_help.instructions')} + values={{ alm: translate('onboarding.alm', alm) }} + /> </p> {url && ( @@ -105,46 +118,60 @@ export default function BitbucketPersonalAccessTokenForm( alt="" // Should be ignored by screen readers className="spacer-right" height="16" - src={`${getBaseUrl()}/images/alm/bitbucket.svg`} + src={`${getBaseUrl()}/images/alm/${alm}.svg`} /> - <a - href={`${url.replace(/\/$/, '')}/plugins/servlet/access-tokens/add`} - rel="noopener noreferrer" - target="_blank"> + <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.bbs_help_2')} + {translate('onboarding.create_project.pat_help.instructions2', alm)} </p> <ul> - <li> - <FormattedMessage - defaultMessage={translate( - 'onboarding.create_project.pat_help.bbs_permission_projects' - )} - id="onboarding.create_project.pat_help.bbs_permission_projects" - values={{ - perm: ( - <strong>{translate('onboarding.create_project.pat_help.read_permission')}</strong> - ) - }} - /> - </li> - <li> - <FormattedMessage - defaultMessage={translate('onboarding.create_project.pat_help.bbs_permission_repos')} - id="onboarding.create_project.pat_help.bbs_permission_repos" - values={{ - perm: ( - <strong>{translate('onboarding.create_project.pat_help.read_permission')}</strong> - ) - }} - /> - </li> + {alm === AlmKeys.Bitbucket && ( + <> + <li> + <FormattedMessage + defaultMessage={translate( + 'onboarding.create_project.pat_help.bbs_permission_projects' + )} + id="onboarding.create_project.pat_help.bbs_permission_projects" + values={{ + perm: ( + <strong> + {translate('onboarding.create_project.pat_help.read_permission')} + </strong> + ) + }} + /> + </li> + <li> + <FormattedMessage + defaultMessage={translate( + 'onboarding.create_project.pat_help.bbs_permission_repos' + )} + id="onboarding.create_project.pat_help.bbs_permission_repos" + values={{ + perm: ( + <strong> + {translate('onboarding.create_project.pat_help.read_permission')} + </strong> + ) + }} + /> + </li> + </> + )} + {alm === AlmKeys.GitLab && ( + <li className="spacer-bottom"> + <strong> + {translate('onboarding.create_project.pat_help.gitlab.read_api_permission')} + </strong> + </li> + )} </ul> </Alert> </div> diff --git a/server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx b/server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx new file mode 100644 index 00000000000..afab4980052 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { AlmKeys } from '../../../types/alm-settings'; +import { ALM_INTEGRATION } from '../../settings/components/AdditionalCategoryKeys'; + +export interface WrongBindingCountAlertProps { + alm: AlmKeys; + canAdmin: boolean; +} + +export default function WrongBindingCountAlert(props: WrongBindingCountAlertProps) { + const { alm, canAdmin } = props; + + return ( + <Alert variant="error"> + {canAdmin ? ( + <FormattedMessage + defaultMessage={translate('onboarding.create_project.wrong_binding_count.admin')} + id="onboarding.create_project.wrong_binding_count.admin" + values={{ + alm: translate('onboarding.alm', alm), + url: ( + <Link + to={{ + pathname: '/admin/settings', + query: { category: ALM_INTEGRATION } + }}> + {translate('settings.page')} + </Link> + ) + }} + /> + ) : ( + <FormattedMessage + defaultMessage={translate('onboarding.create_project.wrong_binding_count')} + id="onboarding.create_project.wrong_binding_count" + values={{ + alm: translate('onboarding.alm', alm) + }} + /> + )} + </Alert> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx index 2e11297a12d..6d0717eecaf 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx @@ -42,10 +42,10 @@ it('should correctly pass the selected mode up', () => { click(wrapper.find('button.create-project-mode-type-manual')); expect(onSelectMode).toBeCalledWith(CreateProjectModes.Manual); - click(wrapper.find('button.create-project-mode-type-bbs').at(0)); + click(wrapper.find('button.create-project-mode-type-alm').at(0)); expect(onSelectMode).toBeCalledWith(CreateProjectModes.BitbucketServer); - click(wrapper.find('button.create-project-mode-type-bbs').at(1)); + click(wrapper.find('button.create-project-mode-type-alm').at(1)); expect(onSelectMode).toBeCalledWith(CreateProjectModes.GitHub); }); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx index 8368b8eb386..e2d16dd4ed7 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx @@ -65,6 +65,13 @@ it('should render correctly if the GitHub method is selected', () => { expect(wrapper).toMatchSnapshot(); }); +it('should render correctly if the GitLab method is selected', () => { + const wrapper = shallowRender({ + location: mockLocation({ query: { mode: CreateProjectModes.GitLab } }) + }); + expect(wrapper).toMatchSnapshot(); +}); + function shallowRender(props: Partial<CreateProjectPage['props']> = {}) { return shallow<CreateProjectPage>( <CreateProjectPage diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx new file mode 100644 index 00000000000..da88c061d27 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; +import { + checkPersonalAccessTokenIsValid, + setAlmPersonalAccessToken +} from '../../../../api/alm-integrations'; +import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; +import { mockLocation } from '../../../../helpers/testMocks'; +import { AlmKeys } from '../../../../types/alm-settings'; +import GitlabProjectCreate from '../GitlabProjectCreate'; + +jest.mock('../../../../api/alm-integrations', () => ({ + checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue(true), + setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null) +})); + +beforeEach(jest.clearAllMocks); + +const almSettingKey = 'gitlab-setting'; + +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(true); + 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(false); + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + expect(wrapper.state().tokenIsValid).toBe(false); +}); + +describe('setting a new PAT', () => { + const wrapper = shallowRender(); + + it('should correctly handle it if invalid', async () => { + (checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(false); + + 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().tokenValidationFailed).toBe(true); + }); + + it('should correctly handle it if valid', async () => { + (checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(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().tokenValidationFailed).toBe(false); + }); +}); + +function shallowRender(props: Partial<GitlabProjectCreate['props']> = {}) { + return shallow<GitlabProjectCreate>( + <GitlabProjectCreate + canAdmin={false} + loadingBindings={false} + location={mockLocation()} + onProjectCreate={jest.fn()} + settings={[mockAlmSettingsInstance({ alm: AlmKeys.GitLab, key: almSettingKey })]} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx new file mode 100644 index 00000000000..c824f5ea041 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; +import { AlmKeys } from '../../../../types/alm-settings'; +import GitlabProjectCreateRenderer, { + GitlabProjectCreateRendererProps +} from '../GitlabProjectCreateRenderer'; + +it('should render correctly', () => { + expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); + expect(shallowRender({ settings: undefined })).toMatchSnapshot('invalid settings'); + expect(shallowRender({ canAdmin: true, settings: undefined })).toMatchSnapshot( + 'invalid settings, admin user' + ); + expect(shallowRender()).toMatchSnapshot('pat form'); +}); + +function shallowRender(props: Partial<GitlabProjectCreateRendererProps> = {}) { + return shallow<GitlabProjectCreateRendererProps>( + <GitlabProjectCreateRenderer + canAdmin={false} + loading={false} + onPersonalAccessTokenCreate={jest.fn()} + showPersonalAccessTokenForm={true} + submittingToken={false} + tokenValidationFailed={false} + settings={mockAlmSettingsInstance({ alm: AlmKeys.GitLab })} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketPersonalAccessTokenForm-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/PersonalAccessTokenForm-test.tsx index 570833989cd..e096aed245d 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketPersonalAccessTokenForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/PersonalAccessTokenForm-test.tsx @@ -24,14 +24,25 @@ 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 { AlmKeys } from '../../../../types/alm-settings'; -import BitbucketPersonalAccessTokenForm, { - BitbucketPersonalAccessTokenFormProps -} from '../BitbucketPersonalAccessTokenForm'; +import PersonalAccessTokenForm, { PersonalAccessTokenFormProps } from '../PersonalAccessTokenForm'; it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender()).toMatchSnapshot('bitbucket'); expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting'); expect(shallowRender({ validationFailed: true })).toMatchSnapshot('validation failed'); + 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' + }) + }) + ).toMatchSnapshot('gitlab with non-standard api path'); }); it('should correctly handle form interactions', () => { @@ -57,10 +68,10 @@ it('should correctly handle form interactions', () => { expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); }); -function shallowRender(props: Partial<BitbucketPersonalAccessTokenFormProps> = {}) { - return shallow<BitbucketPersonalAccessTokenFormProps>( - <BitbucketPersonalAccessTokenForm - bitbucketSetting={mockAlmSettingsInstance({ +function shallowRender(props: Partial<PersonalAccessTokenFormProps> = {}) { + return shallow<PersonalAccessTokenFormProps>( + <PersonalAccessTokenForm + almSetting={mockAlmSettingsInstance({ alm: AlmKeys.Bitbucket, url: 'http://www.example.com' })} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/WrongBindingCountAlert-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/WrongBindingCountAlert-test.tsx new file mode 100644 index 00000000000..9f67f5f3fee --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/WrongBindingCountAlert-test.tsx @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { AlmKeys } from '../../../../types/alm-settings'; +import WrongBindingCountAlert, { WrongBindingCountAlertProps } from '../WrongBindingCountAlert'; + +it('should render correctly', () => { + expect(shallowRender({ canAdmin: true })).toMatchSnapshot('for admin'); + expect(shallowRender({ alm: AlmKeys.Bitbucket })).toMatchSnapshot('bitbucket'); + expect(shallowRender({ alm: AlmKeys.GitLab })).toMatchSnapshot('gitlab'); +}); + +function shallowRender(props: Partial<WrongBindingCountAlertProps> = {}) { + return shallow(<WrongBindingCountAlert alm={AlmKeys.Bitbucket} canAdmin={false} {...props} />); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap index 5b0b50bc580..8d767ba357d 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap @@ -171,32 +171,10 @@ exports[`should render correctly: invalid config, admin user 1`] = ` </span> } /> - <Alert - variant="error" - > - <FormattedMessage - defaultMessage="onboarding.create_project.no_bbs_binding.admin" - id="onboarding.create_project.no_bbs_binding.admin" - values={ - Object { - "url": <Link - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { - "pathname": "/admin/settings", - "query": Object { - "category": "almintegration", - }, - } - } - > - settings.page - </Link>, - } - } - /> - </Alert> + <WrongBindingCountAlert + alm="bitbucket" + canAdmin={true} + /> </Fragment> `; @@ -235,11 +213,10 @@ exports[`should render correctly: invalid config, regular user 1`] = ` </span> } /> - <Alert - variant="error" - > - onboarding.create_project.no_bbs_binding - </Alert> + <WrongBindingCountAlert + alm="bitbucket" + canAdmin={false} + /> </Fragment> `; @@ -302,8 +279,8 @@ exports[`should render correctly: pat form 1`] = ` </span> } /> - <BitbucketPersonalAccessTokenForm - bitbucketSetting={ + <PersonalAccessTokenForm + almSetting={ Object { "alm": "bitbucket", "key": "key", diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap index b0e0b0f9f3d..cd06179f636 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap @@ -36,7 +36,7 @@ exports[`should render correctly: default 1`] = ` </div> </button> <button - className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs" + className="button button-huge big-spacer-left display-flex-column create-project-mode-type-alm" disabled={false} onClick={[Function]} type="button" @@ -53,7 +53,7 @@ exports[`should render correctly: default 1`] = ` </div> </button> <button - className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs disabled" + className="button button-huge big-spacer-left display-flex-column create-project-mode-type-alm disabled" disabled={true} onClick={[Function]} type="button" @@ -83,6 +83,37 @@ exports[`should render correctly: default 1`] = ` /> </div> </button> + <button + className="button button-huge big-spacer-left display-flex-column create-project-mode-type-alm disabled" + disabled={true} + onClick={[Function]} + type="button" + > + <img + alt="" + height={80} + src="/images/alm/gitlab.svg" + /> + <div + className="medium big-spacer-top" + > + onboarding.create_project.select_method.gitlab + </div> + <div + className="text-muted small spacer-top" + style={ + Object { + "lineHeight": 1.5, + } + } + > + onboarding.create_project.alm_not_configured + <HelpTooltip + className="little-spacer-left" + overlay="onboarding.create_project.zero_alm_instances.gitlab" + /> + </div> + </button> </div> </Fragment> `; @@ -123,7 +154,7 @@ exports[`should render correctly: invalid configs 1`] = ` </div> </button> <button - className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs disabled" + className="button button-huge big-spacer-left display-flex-column create-project-mode-type-alm disabled" disabled={true} onClick={[Function]} type="button" @@ -154,7 +185,7 @@ exports[`should render correctly: invalid configs 1`] = ` </div> </button> <button - className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs disabled" + className="button button-huge big-spacer-left display-flex-column create-project-mode-type-alm disabled" disabled={true} onClick={[Function]} type="button" @@ -185,6 +216,37 @@ exports[`should render correctly: invalid configs 1`] = ` /> </div> </button> + <button + className="button button-huge big-spacer-left display-flex-column create-project-mode-type-alm disabled" + disabled={true} + onClick={[Function]} + type="button" + > + <img + alt="" + height={80} + src="/images/alm/gitlab.svg" + /> + <div + className="medium big-spacer-top" + > + onboarding.create_project.select_method.gitlab + </div> + <div + className="text-muted small spacer-top" + style={ + Object { + "lineHeight": 1.5, + } + } + > + onboarding.create_project.alm_not_configured + <HelpTooltip + className="little-spacer-left" + overlay="onboarding.create_project.zero_alm_instances.gitlab" + /> + </div> + </button> </div> </Fragment> `; @@ -225,7 +287,7 @@ exports[`should render correctly: loading instances 1`] = ` </div> </button> <button - className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs disabled" + className="button button-huge big-spacer-left display-flex-column create-project-mode-type-alm disabled" disabled={true} onClick={[Function]} type="button" @@ -248,7 +310,7 @@ exports[`should render correctly: loading instances 1`] = ` </span> </button> <button - className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs disabled" + className="button button-huge big-spacer-left display-flex-column create-project-mode-type-alm disabled" disabled={true} onClick={[Function]} type="button" @@ -270,6 +332,29 @@ exports[`should render correctly: loading instances 1`] = ` /> </span> </button> + <button + className="button button-huge big-spacer-left display-flex-column create-project-mode-type-alm disabled" + disabled={true} + onClick={[Function]} + type="button" + > + <img + alt="" + height={80} + src="/images/alm/gitlab.svg" + /> + <div + className="medium big-spacer-top" + > + onboarding.create_project.select_method.gitlab + </div> + <span> + onboarding.create_project.check_alm_supported + <i + className="little-spacer-left spinner" + /> + </span> + </button> </div> </Fragment> `; diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap index 3a1c1edc18b..3c54a4b2d4d 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap @@ -142,6 +142,44 @@ exports[`should render correctly if the GitHub method is selected 1`] = ` </Fragment> `; +exports[`should render correctly if the GitLab method is selected 1`] = ` +<Fragment> + <Helmet + defer={true} + encodeSpecialCharacters={true} + title="my_account.create_new.TRK" + titleTemplate="%s" + /> + <A11ySkipTarget + anchor="create_project_main" + /> + <div + className="page page-limited huge-spacer-bottom position-relative" + id="create-project" + > + <GitlabProjectCreate + canAdmin={false} + loadingBindings={true} + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object { + "mode": "gitlab", + }, + "search": "", + "state": Object {}, + } + } + onProjectCreate={[Function]} + settings={Array []} + /> + </div> +</Fragment> +`; + exports[`should render correctly if the manual method is selected 1`] = ` <Fragment> <Helmet diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap new file mode 100644 index 00000000000..8bf4d3701b3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<GitlabProjectCreateRenderer + canAdmin={false} + loading={true} + onPersonalAccessTokenCreate={[Function]} + settings={ + Object { + "alm": "gitlab", + "key": "gitlab-setting", + } + } + showPersonalAccessTokenForm={true} + submittingToken={false} + tokenValidationFailed={false} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreateRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreateRenderer-test.tsx.snap new file mode 100644 index 00000000000..d0d2a723737 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreateRenderer-test.tsx.snap @@ -0,0 +1,103 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: invalid settings 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> + } + /> + <WrongBindingCountAlert + alm="gitlab" + canAdmin={false} + /> +</Fragment> +`; + +exports[`should render correctly: invalid settings, admin user 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> + } + /> + <WrongBindingCountAlert + alm="gitlab" + canAdmin={true} + /> +</Fragment> +`; + +exports[`should render correctly: loading 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> + } + /> + <i + className="spinner" + /> +</Fragment> +`; + +exports[`should render correctly: pat form 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} + validationFailed={false} + /> +</Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketPersonalAccessTokenForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/PersonalAccessTokenForm-test.tsx.snap index 18a3bf5e37b..c735dca4150 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketPersonalAccessTokenForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/PersonalAccessTokenForm-test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should render correctly: default 1`] = ` +exports[`should render correctly: bitbucket 1`] = ` <div className="display-flex-start" > @@ -10,12 +10,12 @@ exports[`should render correctly: default 1`] = ` <h2 className="big" > - onboarding.create_project.grant_access_to_bbs.title + onboarding.create_project.pat_form.title.bitbucket </h2> <p className="big-spacer-top big-spacer-bottom" > - onboarding.create_project.grant_access_to_bbs.help + onboarding.create_project.pat_form.help.bitbucket </p> <ValidationInput id="personal_access_token" @@ -56,7 +56,15 @@ exports[`should render correctly: default 1`] = ` <p className="big-spacer-top big-spacer-bottom" > - onboarding.create_project.pat_help.bbs_help_1 + <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" @@ -78,7 +86,7 @@ exports[`should render correctly: default 1`] = ` <p className="big-spacer-top big-spacer-bottom" > - onboarding.create_project.pat_help.bbs_help_2 + onboarding.create_project.pat_help.instructions2.bitbucket </p> <ul> <li> @@ -112,6 +120,208 @@ exports[`should render correctly: default 1`] = ` </div> `; +exports[`should render correctly: gitlab 1`] = ` +<div + className="display-flex-start" +> + <form + onSubmit={[Function]} + > + <h2 + className="big" + > + onboarding.create_project.pat_form.title.gitlab + </h2> + <p + className="big-spacer-top big-spacer-bottom" + > + onboarding.create_project.pat_form.help.gitlab + </p> + <ValidationInput + id="personal_access_token" + isInvalid={false} + isValid={false} + label="onboarding.create_project.enter_pat" + required={true} + > + <input + autoFocus={true} + className="input-super-large" + id="personal_access_token" + minLength={1} + name="personal_access_token" + onChange={[Function]} + type="text" + /> + </ValidationInput> + <SubmitButton + disabled={true} + > + save + </SubmitButton> + <DeferredSpinner + className="spacer-left" + loading={false} + timeout={100} + /> + </form> + <Alert + className="big-spacer-left big-spacer-top" + display="block" + variant="info" + > + <h3> + onboarding.create_project.pat_help.title + </h3> + <p + className="big-spacer-top big-spacer-bottom" + > + <FormattedMessage + defaultMessage="onboarding.create_project.pat_help.instructions" + id="onboarding.create_project.pat_help.instructions" + values={ + Object { + "alm": "onboarding.alm.gitlab", + } + } + /> + </p> + <div + className="text-middle" + > + <img + alt="" + className="spacer-right" + height="16" + src="/images/alm/gitlab.svg" + /> + <a + href="https://gitlab.com/profile/personal_access_tokens" + 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.gitlab + </p> + <ul> + <li + className="spacer-bottom" + > + <strong> + onboarding.create_project.pat_help.gitlab.read_api_permission + </strong> + </li> + </ul> + </Alert> +</div> +`; + +exports[`should render correctly: gitlab with non-standard api path 1`] = ` +<div + className="display-flex-start" +> + <form + onSubmit={[Function]} + > + <h2 + className="big" + > + onboarding.create_project.pat_form.title.gitlab + </h2> + <p + className="big-spacer-top big-spacer-bottom" + > + onboarding.create_project.pat_form.help.gitlab + </p> + <ValidationInput + id="personal_access_token" + isInvalid={false} + isValid={false} + label="onboarding.create_project.enter_pat" + required={true} + > + <input + autoFocus={true} + className="input-super-large" + id="personal_access_token" + minLength={1} + name="personal_access_token" + onChange={[Function]} + type="text" + /> + </ValidationInput> + <SubmitButton + disabled={true} + > + save + </SubmitButton> + <DeferredSpinner + className="spacer-left" + loading={false} + timeout={100} + /> + </form> + <Alert + className="big-spacer-left big-spacer-top" + display="block" + variant="info" + > + <h3> + onboarding.create_project.pat_help.title + </h3> + <p + className="big-spacer-top big-spacer-bottom" + > + <FormattedMessage + defaultMessage="onboarding.create_project.pat_help.instructions" + id="onboarding.create_project.pat_help.instructions" + values={ + Object { + "alm": "onboarding.alm.gitlab", + } + } + /> + </p> + <div + className="text-middle" + > + <img + alt="" + className="spacer-right" + height="16" + src="/images/alm/gitlab.svg" + /> + <a + href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token" + 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.gitlab + </p> + <ul> + <li + className="spacer-bottom" + > + <strong> + onboarding.create_project.pat_help.gitlab.read_api_permission + </strong> + </li> + </ul> + </Alert> +</div> +`; + exports[`should render correctly: submitting 1`] = ` <div className="display-flex-start" @@ -122,12 +332,12 @@ exports[`should render correctly: submitting 1`] = ` <h2 className="big" > - onboarding.create_project.grant_access_to_bbs.title + onboarding.create_project.pat_form.title.bitbucket </h2> <p className="big-spacer-top big-spacer-bottom" > - onboarding.create_project.grant_access_to_bbs.help + onboarding.create_project.pat_form.help.bitbucket </p> <ValidationInput id="personal_access_token" @@ -168,7 +378,15 @@ exports[`should render correctly: submitting 1`] = ` <p className="big-spacer-top big-spacer-bottom" > - onboarding.create_project.pat_help.bbs_help_1 + <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" @@ -190,7 +408,7 @@ exports[`should render correctly: submitting 1`] = ` <p className="big-spacer-top big-spacer-bottom" > - onboarding.create_project.pat_help.bbs_help_2 + onboarding.create_project.pat_help.instructions2.bitbucket </p> <ul> <li> @@ -234,12 +452,12 @@ exports[`should render correctly: validation failed 1`] = ` <h2 className="big" > - onboarding.create_project.grant_access_to_bbs.title + onboarding.create_project.pat_form.title.bitbucket </h2> <p className="big-spacer-top big-spacer-bottom" > - onboarding.create_project.grant_access_to_bbs.help + onboarding.create_project.pat_form.help.bitbucket </p> <ValidationInput error="onboarding.create_project.pat_incorrect" @@ -281,7 +499,15 @@ exports[`should render correctly: validation failed 1`] = ` <p className="big-spacer-top big-spacer-bottom" > - onboarding.create_project.pat_help.bbs_help_1 + <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" @@ -303,7 +529,7 @@ exports[`should render correctly: validation failed 1`] = ` <p className="big-spacer-top big-spacer-bottom" > - onboarding.create_project.pat_help.bbs_help_2 + onboarding.create_project.pat_help.instructions2.bitbucket </p> <ul> <li> diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/WrongBindingCountAlert-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/WrongBindingCountAlert-test.tsx.snap new file mode 100644 index 00000000000..b30756a2619 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/WrongBindingCountAlert-test.tsx.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: bitbucket 1`] = ` +<Alert + variant="error" +> + <FormattedMessage + defaultMessage="onboarding.create_project.wrong_binding_count" + id="onboarding.create_project.wrong_binding_count" + values={ + Object { + "alm": "onboarding.alm.bitbucket", + } + } + /> +</Alert> +`; + +exports[`should render correctly: for admin 1`] = ` +<Alert + variant="error" +> + <FormattedMessage + defaultMessage="onboarding.create_project.wrong_binding_count.admin" + id="onboarding.create_project.wrong_binding_count.admin" + values={ + Object { + "alm": "onboarding.alm.bitbucket", + "url": <Link + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/admin/settings", + "query": Object { + "category": "almintegration", + }, + } + } + > + settings.page + </Link>, + } + } + /> +</Alert> +`; + +exports[`should render correctly: gitlab 1`] = ` +<Alert + variant="error" +> + <FormattedMessage + defaultMessage="onboarding.create_project.wrong_binding_count" + id="onboarding.create_project.wrong_binding_count" + values={ + Object { + "alm": "onboarding.alm.gitlab", + } + } + /> +</Alert> +`; diff --git a/server/sonar-web/src/main/js/apps/create/project/types.ts b/server/sonar-web/src/main/js/apps/create/project/types.ts index 0fe8944a405..c84dd2c1d4c 100644 --- a/server/sonar-web/src/main/js/apps/create/project/types.ts +++ b/server/sonar-web/src/main/js/apps/create/project/types.ts @@ -20,5 +20,6 @@ export enum CreateProjectModes { Manual = 'manual', BitbucketServer = 'bitbucket', - GitHub = 'github' + GitHub = 'github', + GitLab = 'gitlab' } diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index e45d4af8035..e051a4109ca 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1800,8 +1800,9 @@ my_account.create_new.VW=Create Portfolio my_account.create_new.APP=Create Application my_account.add_project=Add Project my_account.add_project.manual=Manually -my_account.add_project.github=GitHub my_account.add_project.bitbucket=Bitbucket +my_account.add_project.github=GitHub +my_account.add_project.gitlab=GitLab my_account.create_new_project_portfolio_or_application=Analyze new project / Create new portfolio or application @@ -3095,6 +3096,9 @@ footer.web_api=Web API # ONBOARDING # #------------------------------------------------------------------------------ +onboarding.alm.bitbucket=Bitbucket Server +onboarding.alm.gitlab=GitLab + onboarding.project_analysis.header=Analyze your project onboarding.project_analysis.description=We initialized your project on {instance}, now it's up to you to launch analyses! onboarding.project_analysis.guide_to_integrate_pipelines=follow the guide to integrating with Pipelines @@ -3103,6 +3107,7 @@ onboarding.create_project.setup_manually=Create a project onboarding.create_project.select_method.manual=Manually onboarding.create_project.select_method.bitbucket=From Bitbucket Server onboarding.create_project.select_method.github=From GitHub +onboarding.create_project.select_method.gitlab=From GitLab onboarding.create_project.alm_not_configured=Currently not active onboarding.create_project.check_alm_supported=Checking if available onboarding.create_project.project_key=Project key @@ -3125,25 +3130,33 @@ onboarding.create_project.search_repositories=Search for a repository onboarding.create_project.select_repositories=Select repositories onboarding.create_project.select_all_repositories=Select all available repositories onboarding.create_project.from_bbs=Create a project from Bitbucket Server -onboarding.create_project.grant_access_to_bbs.title=Grant access to your repositories -onboarding.create_project.grant_access_to_bbs.help=SonarQube needs a personal access token to access and list your repositories from Bitbucket Server. + +onboarding.create_project.pat_form.title.bitbucket=Grant access to your repositories +onboarding.create_project.pat_form.title.gitlab=Grant access to your projects +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.gitlab=SonarQube needs a personal access token to access and list your projects from GitLab. onboarding.create_project.select_method=How do you want to create your project? onboarding.create_project.too_many_alm_instances.bitbucket=You must have exactly 1 Bitbucket Server instance configured in order to use this method. onboarding.create_project.too_many_alm_instances.github=You must have exactly 1 Bitbucket Server instance configured in order to use this method. onboarding.create_project.alm_instances_count_X=You currently have {0}. onboarding.create_project.zero_alm_instances.bitbucket=You must first configure a Bitbucket Server instance. onboarding.create_project.zero_alm_instances.github=You must first configure a GitHub instance. -onboarding.create_project.no_bbs_binding=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method, but none were found. Either create the project manually, or contact your system administrator. -onboarding.create_project.no_bbs_binding.admin=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method. You can configure instances under {url}. +onboarding.create_project.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.pat_incorrect=Your personal access token failed to validate. onboarding.create_project.pat_help.title=How to create a personal access token? -onboarding.create_project.pat_help.bbs_help_1=Click the following link to generate a token in Bitbucket Server, and copy-paste it into the personal access token field. -onboarding.create_project.pat_help.bbs_help_2=Set a name, for example "SonarQube", and select the following permissions: + +onboarding.create_project.pat_help.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.instructions2.bitbucket=Set a name, for example "SonarQube", and select the following permissions: onboarding.create_project.pat_help.link=Create personal access token onboarding.create_project.pat_help.bbs_permission_projects=Projects: {perm} onboarding.create_project.pat_help.bbs_permission_repos=Repositories: {perm} onboarding.create_project.pat_help.read_permission=Read + +onboarding.create_project.pat_help.instructions2.gitlab=Set a name, for example "SonarQube", and select the following scope: +onboarding.create_project.pat_help.gitlab.read_api_permission=read_api + onboarding.create_project.no_bbs_projects=No projects could be fetched from Bitbucket Server. Contact your system administrator, or {link}. onboarding.create_project.no_bbs_repos=No repositories were found for this project. Contact your system administrator, or {link}. onboarding.create_project.update_your_token=update your personal access token @@ -3159,6 +3172,7 @@ onboarding.create_project.github.warning.message_admin=Please make sure the GitH onboarding.create_project.github.warning.message_admin.link=ALM integration settings onboarding.create_project.github.no_orgs=We couldn't load any organizations with your key. Contact an administrator. onboarding.create_project.github.no_orgs_admin=We couldn't load any organizations. Make sure the GitHub App is installed in at least one organization and check the GitHub instance configuration in the {link}. +onboarding.create_project.gitlab.title=Which GitLab project do you want to setup? onboarding.create_organization.page.header=Create Organization onboarding.create_organization.page.description=An organization is a space where a team or a whole company can collaborate accross many projects. |