From c9460063e843eef0c94a442c3326c4d24d1a921b Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 22 Jul 2020 10:57:38 +0200 Subject: [PATCH] SONAR-13628 Add PAT form for GitLab onboarding --- .../components/nav/global/GlobalNavPlus.tsx | 2 +- .../BitbucketProjectCreateRenderer.tsx | 35 +-- .../project/CreateProjectModeSelection.tsx | 3 +- .../apps/create/project/CreateProjectPage.tsx | 20 +- .../create/project/GitlabProjectCreate.tsx | 144 ++++++++++ .../project/GitlabProjectCreateRenderer.tsx | 84 ++++++ ...enForm.tsx => PersonalAccessTokenForm.tsx} | 107 +++++--- .../create/project/WrongBindingCountAlert.tsx | 66 +++++ .../CreateProjectModeSelection-test.tsx | 4 +- .../__tests__/CreateProjectPage-test.tsx | 7 + .../__tests__/GitlabProjectCreate-test.tsx | 116 ++++++++ .../GitlabProjectCreateRenderer-test.tsx | 51 ++++ ...t.tsx => PersonalAccessTokenForm-test.tsx} | 27 +- .../__tests__/WrongBindingCountAlert-test.tsx | 34 +++ ...tbucketProjectCreateRenderer-test.tsx.snap | 43 +-- .../CreateProjectModeSelection-test.tsx.snap | 97 ++++++- .../CreateProjectPage-test.tsx.snap | 38 +++ .../GitlabProjectCreate-test.tsx.snap | 18 ++ .../GitlabProjectCreateRenderer-test.tsx.snap | 103 +++++++ ... => PersonalAccessTokenForm-test.tsx.snap} | 252 +++++++++++++++++- .../WrongBindingCountAlert-test.tsx.snap | 63 +++++ .../src/main/js/apps/create/project/types.ts | 3 +- .../resources/org/sonar/l10n/core.properties | 28 +- 23 files changed, 1201 insertions(+), 144 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx create mode 100644 server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx rename server/sonar-web/src/main/js/apps/create/project/{BitbucketPersonalAccessTokenForm.tsx => PersonalAccessTokenForm.tsx} (57%) create mode 100644 server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx create mode 100644 server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx rename server/sonar-web/src/main/js/apps/create/project/__tests__/{BitbucketPersonalAccessTokenForm-test.tsx => PersonalAccessTokenForm-test.tsx} (77%) create mode 100644 server/sonar-web/src/main/js/apps/create/project/__tests__/WrongBindingCountAlert-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreateRenderer-test.tsx.snap rename server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/{BitbucketPersonalAccessTokenForm-test.tsx.snap => PersonalAccessTokenForm-test.tsx.snap} (55%) create mode 100644 server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/WrongBindingCountAlert-test.tsx.snap 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/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/BitbucketPersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx similarity index 57% rename from server/sonar-web/src/main/js/apps/create/project/BitbucketPersonalAccessTokenForm.tsx rename to 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); }}> -

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

+

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

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

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

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

{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`} /> - + {translate('onboarding.create_project.pat_help.link')} )}

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

    -
  • - {translate('onboarding.create_project.pat_help.read_permission')} - ) - }} - /> -
  • -
  • - {translate('onboarding.create_project.pat_help.read_permission')} - ) - }} - /> -
  • + {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__/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__/BitbucketPersonalAccessTokenForm-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/PersonalAccessTokenForm-test.tsx similarity index 77% rename from server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketPersonalAccessTokenForm-test.tsx rename to 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 = {}) { - return shallow( - = {}) { + return shallow( + { + 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__/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__/BitbucketPersonalAccessTokenForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/PersonalAccessTokenForm-test.tsx.snap similarity index 55% rename from server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketPersonalAccessTokenForm-test.tsx.snap rename to 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`] = `
@@ -10,12 +10,12 @@ exports[`should render correctly: default 1`] = `

- onboarding.create_project.grant_access_to_bbs.title + onboarding.create_project.pat_form.title.bitbucket

- onboarding.create_project.grant_access_to_bbs.help + onboarding.create_project.pat_form.help.bitbucket

- onboarding.create_project.pat_help.bbs_help_1 +

- onboarding.create_project.pat_help.bbs_help_2 + onboarding.create_project.pat_help.instructions2.bitbucket

  • @@ -112,6 +120,208 @@ exports[`should render correctly: default 1`] = `
`; +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.grant_access_to_bbs.title + onboarding.create_project.pat_form.title.bitbucket

- onboarding.create_project.grant_access_to_bbs.help + onboarding.create_project.pat_form.help.bitbucket

- onboarding.create_project.pat_help.bbs_help_1 +

- onboarding.create_project.pat_help.bbs_help_2 + onboarding.create_project.pat_help.instructions2.bitbucket

  • @@ -234,12 +452,12 @@ exports[`should render correctly: validation failed 1`] = `

    - onboarding.create_project.grant_access_to_bbs.title + onboarding.create_project.pat_form.title.bitbucket

    - onboarding.create_project.grant_access_to_bbs.help + onboarding.create_project.pat_form.help.bitbucket

    - onboarding.create_project.pat_help.bbs_help_1 +

    - onboarding.create_project.pat_help.bbs_help_2 + onboarding.create_project.pat_help.instructions2.bitbucket

    • 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. -- 2.39.5