From: Jeremy Davis Date: Wed, 22 Jul 2020 08:57:38 +0000 (+0200) Subject: SONAR-13628 Add PAT form for GitLab onboarding X-Git-Tag: 8.5.0.37579~145 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=c9460063e843eef0c94a442c3326c4d24d1a921b;p=sonarqube.git SONAR-13628 Add PAT form for GitLab onboarding --- 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 { mounted = false; diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketPersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketPersonalAccessTokenForm.tsx deleted file mode 100644 index 4f6826be68a..00000000000 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketPersonalAccessTokenForm.tsx +++ /dev/null @@ -1,152 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; -import { AlmSettingsInstance } from '../../../types/alm-settings'; - -export interface BitbucketPersonalAccessTokenFormProps { - bitbucketSetting: AlmSettingsInstance; - onPersonalAccessTokenCreate: (token: string) => void; - submitting?: boolean; - validationFailed: boolean; -} - -export default function BitbucketPersonalAccessTokenForm( - props: BitbucketPersonalAccessTokenFormProps -) { - const { - bitbucketSetting: { url }, - submitting = false, - validationFailed - } = props; - const [touched, setTouched] = React.useState(false); - - React.useEffect(() => { - setTouched(false); - }, [submitting]); - - const isInvalid = validationFailed && !touched; - - return ( -
-
) => { - e.preventDefault(); - const value = new FormData(e.currentTarget).get('personal_access_token') as string; - props.onPersonalAccessTokenCreate(value); - }}> -

{translate('onboarding.create_project.grant_access_to_bbs.title')}

-

- {translate('onboarding.create_project.grant_access_to_bbs.help')} -

- - - { - setTouched(true); - }} - type="text" - /> - - - - {translate('save')} - - - - - -

{translate('onboarding.create_project.pat_help.title')}

- -

- {translate('onboarding.create_project.pat_help.bbs_help_1')} -

- - {url && ( - - )} - -

- {translate('onboarding.create_project.pat_help.bbs_help_2')} -

- -
    -
  • - {translate('onboarding.create_project.pat_help.read_permission')} - ) - }} - /> -
  • -
  • - {translate('onboarding.create_project.pat_help.read_permission')} - ) - }} - /> -
  • -
-
-
- ); -} 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 && } {!loading && !bitbucketSetting && ( - - {canAdmin ? ( - - {translate('settings.page')} - - ) - }} - /> - ) : ( - translate('onboarding.create_project.no_bbs_binding') - )} - + )} {!loading && bitbucketSetting && (showPersonalAccessTokenForm ? ( - ); 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 { interface State { bitbucketSettings: AlmSettingsInstance[]; githubSettings: AlmSettingsInstance[]; + gitlabSettings: AlmSettingsInstance[]; loading: boolean; } export class CreateProjectPage extends React.PureComponent { 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 { 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 { 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 { /> ); } + case CreateProjectModes.GitLab: { + return ( + + ); + } case CreateProjectModes.Manual: { return ; } @@ -136,7 +150,7 @@ export class CreateProjectPage extends React.PureComponent { [AlmKeys.Azure]: 0, [AlmKeys.Bitbucket]: bitbucketSettings.length, [AlmKeys.GitHub]: githubSettings.length, - [AlmKeys.GitLab]: 0 + [AlmKeys.GitLab]: gitlabSettings.length }; return ( { + 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 { + 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 ( + + ); + } +} 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 ( + <> + + + {translate('onboarding.create_project.gitlab.title')} + + } + /> + + {loading && } + + {!loading && !settings && ( + + )} + + {!loading && + settings && + (showPersonalAccessTokenForm ? ( + + ) : ( +
Token is valid!
+ ))} + + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx new file mode 100644 index 00000000000..2a0342a5d33 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx @@ -0,0 +1,179 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as classNames from 'classnames'; +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; +import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; +import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; + +export interface PersonalAccessTokenFormProps { + almSetting: AlmSettingsInstance; + onPersonalAccessTokenCreate: (token: string) => void; + submitting?: boolean; + validationFailed: boolean; +} + +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 { + almSetting: { alm, url }, + submitting = false, + validationFailed + } = props; + const [touched, setTouched] = React.useState(false); + + React.useEffect(() => { + setTouched(false); + }, [submitting]); + + const isInvalid = validationFailed && !touched; + + return ( +
+
) => { + e.preventDefault(); + const value = new FormData(e.currentTarget).get('personal_access_token') as string; + props.onPersonalAccessTokenCreate(value); + }}> +

{translate('onboarding.create_project.pat_form.title', alm)}

+

+ {translate('onboarding.create_project.pat_form.help', alm)} +

+ + + { + setTouched(true); + }} + type="text" + /> + + + + {translate('save')} + + + + + +

{translate('onboarding.create_project.pat_help.title')}

+ +

+ +

+ + {url && ( + + )} + +

+ {translate('onboarding.create_project.pat_help.instructions2', alm)} +

+ +
    + {alm === AlmKeys.Bitbucket && ( + <> +
  • + + {translate('onboarding.create_project.pat_help.read_permission')} + + ) + }} + /> +
  • +
  • + + {translate('onboarding.create_project.pat_help.read_permission')} + + ) + }} + /> +
  • + + )} + {alm === AlmKeys.GitLab && ( +
  • + + {translate('onboarding.create_project.pat_help.gitlab.read_api_permission')} + +
  • + )} +
+
+
+ ); +} 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 ( + + {canAdmin ? ( + + {translate('settings.page')} + + ) + }} + /> + ) : ( + + )} + + ); +} 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__/BitbucketPersonalAccessTokenForm-test.tsx deleted file mode 100644 index 570833989cd..00000000000 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketPersonalAccessTokenForm-test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import { change, submit } from 'sonar-ui-common/helpers/testUtils'; -import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; -import { AlmKeys } from '../../../../types/alm-settings'; -import BitbucketPersonalAccessTokenForm, { - BitbucketPersonalAccessTokenFormProps -} from '../BitbucketPersonalAccessTokenForm'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting'); - expect(shallowRender({ validationFailed: true })).toMatchSnapshot('validation failed'); -}); - -it('should correctly handle form interactions', () => { - const onPersonalAccessTokenCreate = jest.fn(); - const wrapper = shallowRender({ onPersonalAccessTokenCreate }); - - // Submit button disabled by default. - expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); - - // Submit button enabled if there's a value. - change(wrapper.find('input'), 'token'); - expect(wrapper.find(SubmitButton).prop('disabled')).toBe(false); - - // Expect correct calls to be made when submitting. - submit(wrapper.find('form')); - expect(onPersonalAccessTokenCreate).toBeCalled(); - - // 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 }); - expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} 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 = {}) { return shallow( ({ + 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 = {}) { + return shallow( + + ); +} 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 = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/PersonalAccessTokenForm-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/PersonalAccessTokenForm-test.tsx new file mode 100644 index 00000000000..e096aed245d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/PersonalAccessTokenForm-test.tsx @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; +import { change, submit } from 'sonar-ui-common/helpers/testUtils'; +import { mockAlmSettingsInstance } 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({ + 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', () => { + const onPersonalAccessTokenCreate = jest.fn(); + const wrapper = shallowRender({ onPersonalAccessTokenCreate }); + + // Submit button disabled by default. + expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); + + // Submit button enabled if there's a value. + change(wrapper.find('input'), 'token'); + expect(wrapper.find(SubmitButton).prop('disabled')).toBe(false); + + // Expect correct calls to be made when submitting. + submit(wrapper.find('form')); + expect(onPersonalAccessTokenCreate).toBeCalled(); + + // 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 }); + expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} 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 = {}) { + return shallow(); +} 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__/BitbucketPersonalAccessTokenForm-test.tsx.snap deleted file mode 100644 index 18a3bf5e37b..00000000000 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketPersonalAccessTokenForm-test.tsx.snap +++ /dev/null @@ -1,338 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: default 1`] = ` -
-
-

- onboarding.create_project.grant_access_to_bbs.title -

-

- onboarding.create_project.grant_access_to_bbs.help -

- - - - - save - - - - -

- onboarding.create_project.pat_help.title -

-

- onboarding.create_project.pat_help.bbs_help_1 -

- -

- onboarding.create_project.pat_help.bbs_help_2 -

-
    -
  • - - onboarding.create_project.pat_help.read_permission - , - } - } - /> -
  • -
  • - - onboarding.create_project.pat_help.read_permission - , - } - } - /> -
  • -
-
-
-`; - -exports[`should render correctly: submitting 1`] = ` -
-
-

- onboarding.create_project.grant_access_to_bbs.title -

-

- onboarding.create_project.grant_access_to_bbs.help -

- - - - - save - - - - -

- onboarding.create_project.pat_help.title -

-

- onboarding.create_project.pat_help.bbs_help_1 -

- -

- onboarding.create_project.pat_help.bbs_help_2 -

-
    -
  • - - onboarding.create_project.pat_help.read_permission - , - } - } - /> -
  • -
  • - - onboarding.create_project.pat_help.read_permission - , - } - } - /> -
  • -
-
-
-`; - -exports[`should render correctly: validation failed 1`] = ` -
-
-

- onboarding.create_project.grant_access_to_bbs.title -

-

- onboarding.create_project.grant_access_to_bbs.help -

- - - - - save - - - - -

- onboarding.create_project.pat_help.title -

-

- onboarding.create_project.pat_help.bbs_help_1 -

- -

- onboarding.create_project.pat_help.bbs_help_2 -

-
    -
  • - - onboarding.create_project.pat_help.read_permission - , - } - } - /> -
  • -
  • - - onboarding.create_project.pat_help.read_permission - , - } - } - /> -
  • -
-
-
-`; 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`] = ` } /> - - - settings.page - , - } - } - /> - + `; @@ -235,11 +213,10 @@ exports[`should render correctly: invalid config, regular user 1`] = ` } /> - - onboarding.create_project.no_bbs_binding - + `; @@ -302,8 +279,8 @@ exports[`should render correctly: pat form 1`] = ` } /> - + `; @@ -123,7 +154,7 @@ exports[`should render correctly: invalid configs 1`] = ` + `; @@ -225,7 +287,7 @@ exports[`should render correctly: loading instances 1`] = ` + `; 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`] = ` `; +exports[`should render correctly if the GitLab method is selected 1`] = ` + + + +
+ +
+
+`; + exports[`should render correctly if the manual method is selected 1`] = ` +`; 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`] = ` + + + + onboarding.create_project.gitlab.title + + } + /> + + +`; + +exports[`should render correctly: invalid settings, admin user 1`] = ` + + + + onboarding.create_project.gitlab.title + + } + /> + + +`; + +exports[`should render correctly: loading 1`] = ` + + + + onboarding.create_project.gitlab.title + + } + /> + + +`; + +exports[`should render correctly: pat form 1`] = ` + + + + onboarding.create_project.gitlab.title + + } + /> + + +`; diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/PersonalAccessTokenForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/PersonalAccessTokenForm-test.tsx.snap new file mode 100644 index 00000000000..c735dca4150 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/PersonalAccessTokenForm-test.tsx.snap @@ -0,0 +1,564 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: bitbucket 1`] = ` +
+
+

+ onboarding.create_project.pat_form.title.bitbucket +

+

+ onboarding.create_project.pat_form.help.bitbucket +

+ + + + + save + + + + +

+ onboarding.create_project.pat_help.title +

+

+ +

+ +

+ onboarding.create_project.pat_help.instructions2.bitbucket +

+
    +
  • + + onboarding.create_project.pat_help.read_permission + , + } + } + /> +
  • +
  • + + onboarding.create_project.pat_help.read_permission + , + } + } + /> +
  • +
+
+
+`; + +exports[`should render correctly: gitlab 1`] = ` +
+
+

+ onboarding.create_project.pat_form.title.gitlab +

+

+ onboarding.create_project.pat_form.help.gitlab +

+ + + + + save + + + + +

+ onboarding.create_project.pat_help.title +

+

+ +

+ +

+ onboarding.create_project.pat_help.instructions2.gitlab +

+
    +
  • + + onboarding.create_project.pat_help.gitlab.read_api_permission + +
  • +
+
+
+`; + +exports[`should render correctly: gitlab with non-standard api path 1`] = ` +
+
+

+ onboarding.create_project.pat_form.title.gitlab +

+

+ onboarding.create_project.pat_form.help.gitlab +

+ + + + + save + + + + +

+ onboarding.create_project.pat_help.title +

+

+ +

+ +

+ onboarding.create_project.pat_help.instructions2.gitlab +

+
    +
  • + + onboarding.create_project.pat_help.gitlab.read_api_permission + +
  • +
+
+
+`; + +exports[`should render correctly: submitting 1`] = ` +
+
+

+ onboarding.create_project.pat_form.title.bitbucket +

+

+ onboarding.create_project.pat_form.help.bitbucket +

+ + + + + save + + + + +

+ onboarding.create_project.pat_help.title +

+

+ +

+ +

+ onboarding.create_project.pat_help.instructions2.bitbucket +

+
    +
  • + + onboarding.create_project.pat_help.read_permission + , + } + } + /> +
  • +
  • + + onboarding.create_project.pat_help.read_permission + , + } + } + /> +
  • +
+
+
+`; + +exports[`should render correctly: validation failed 1`] = ` +
+
+

+ onboarding.create_project.pat_form.title.bitbucket +

+

+ onboarding.create_project.pat_form.help.bitbucket +

+ + + + + save + + + + +

+ onboarding.create_project.pat_help.title +

+

+ +

+ +

+ onboarding.create_project.pat_help.instructions2.bitbucket +

+
    +
  • + + onboarding.create_project.pat_help.read_permission + , + } + } + /> +
  • +
  • + + onboarding.create_project.pat_help.read_permission + , + } + } + /> +
  • +
+
+
+`; 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`] = ` + + + +`; + +exports[`should render correctly: for admin 1`] = ` + + + settings.page + , + } + } + /> + +`; + +exports[`should render correctly: gitlab 1`] = ` + + + +`; 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.