From: Revanshu Paliwal Date: Fri, 11 Nov 2022 15:12:19 +0000 (+0100) Subject: SONAR-17586 Allow project onboarding when multiple Gitlab integrations are configured X-Git-Tag: 9.8.0.63668~113 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=317ada58ae514beeb7865d67dae54f271f6d26ae;p=sonarqube.git SONAR-17586 Allow project onboarding when multiple Gitlab integrations are configured --- diff --git a/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts new file mode 100644 index 00000000000..d63bbf29a17 --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 { cloneDeep } from 'lodash'; +import { mockGitlabProject } from '../../helpers/mocks/alm-integrations'; +import { GitlabProject } from '../../types/alm-integration'; +import { + checkPersonalAccessTokenIsValid, + getGitlabProjects, + setAlmPersonalAccessToken, +} from '../alm-integrations'; + +export default class AlmIntegrationsServiceMock { + almInstancePATMap: { [key: string]: boolean } = {}; + gitlabProjects: GitlabProject[]; + defaultAlmInstancePATMap: { [key: string]: boolean } = { + 'conf-final-1': false, + 'conf-final-2': true, + }; + + defaultGitlabProjects: GitlabProject[] = [ + mockGitlabProject({ + name: 'Gitlab project 1', + id: '1', + sqProjectKey: 'key', + sqProjectName: 'Gitlab project 1', + }), + mockGitlabProject({ name: 'Gitlab project 2', id: '2' }), + mockGitlabProject({ name: 'Gitlab project 3', id: '3' }), + ]; + + constructor() { + this.almInstancePATMap = cloneDeep(this.defaultAlmInstancePATMap); + this.gitlabProjects = cloneDeep(this.defaultGitlabProjects); + (checkPersonalAccessTokenIsValid as jest.Mock).mockImplementation( + this.checkPersonalAccessTokenIsValid + ); + (setAlmPersonalAccessToken as jest.Mock).mockImplementation(this.setAlmPersonalAccessToken); + (getGitlabProjects as jest.Mock).mockImplementation(this.getGitlabProjects); + } + + checkPersonalAccessTokenIsValid = (conf: string) => { + return Promise.resolve({ status: this.almInstancePATMap[conf] }); + }; + + setAlmPersonalAccessToken = (conf: string) => { + this.almInstancePATMap[conf] = true; + return Promise.resolve(); + }; + + getGitlabProjects = () => { + return Promise.resolve({ + projects: this.gitlabProjects, + projectsPaging: { + pageIndex: 1, + pageSize: 30, + total: 3, + }, + }); + }; + + setGitlabProjects(gitlabProjects: GitlabProject[]) { + this.gitlabProjects = gitlabProjects; + } + + reset = () => { + this.almInstancePATMap = cloneDeep(this.defaultAlmInstancePATMap); + this.gitlabProjects = cloneDeep(this.defaultGitlabProjects); + }; +} diff --git a/server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts new file mode 100644 index 00000000000..9fb8b1bc6b2 --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 { cloneDeep } from 'lodash'; +import { mockAlmSettingsInstance } from '../../helpers/mocks/alm-settings'; +import { AlmKeys, AlmSettingsInstance } from '../../types/alm-settings'; +import { getAlmSettings } from '../alm-settings'; + +export default class AlmSettingsServiceMock { + almSettings: AlmSettingsInstance[]; + defaultSetting: AlmSettingsInstance[] = [ + mockAlmSettingsInstance({ key: 'conf-final-1', alm: AlmKeys.GitLab }), + mockAlmSettingsInstance({ key: 'conf-final-2', alm: AlmKeys.GitLab }), + ]; + + constructor() { + this.almSettings = cloneDeep(this.defaultSetting); + (getAlmSettings as jest.Mock).mockImplementation(this.getAlmSettingsHandler); + } + + getAlmSettingsHandler = () => { + return Promise.resolve(this.almSettings); + }; + + reset = () => { + this.almSettings = cloneDeep(this.defaultSetting); + }; +} 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 7e4c9c0c007..22f27994a94 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 @@ -28,6 +28,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; import { AlmKeys } from '../../../types/alm-settings'; import { AppState } from '../../../types/appstate'; +import { ALLOWED_MULTIPLE_CONFIGS } from './constants'; import { CreateProjectModes } from './types'; export interface CreateProjectModeSelectionProps { @@ -42,6 +43,32 @@ export interface CreateProjectModeSelectionProps { const DEFAULT_ICON_SIZE = 50; +function getErrorMessage( + hasTooManyConfig: boolean, + hasConfig: boolean, + canAdmin: boolean | undefined, + alm: AlmKeys +) { + if (hasTooManyConfig) { + return translateWithParameters( + 'onboarding.create_project.too_many_alm_instances_X', + translate('alm', alm) + ); + } else if (!hasConfig) { + return canAdmin + ? translate('onboarding.create_project.alm_not_configured.admin') + : translate('onboarding.create_project.alm_not_configured'); + } +} + +function getMode( + isBitbucketOption: boolean, + hasBitbucketCloudConf: boolean, + mode: CreateProjectModes +) { + return isBitbucketOption && hasBitbucketCloudConf ? CreateProjectModes.BitbucketCloud : mode; +} + function renderAlmOption( props: CreateProjectModeSelectionProps, alm: AlmKeys.Azure | AlmKeys.BitbucketServer | AlmKeys.GitHub | AlmKeys.GitLab, @@ -61,7 +88,7 @@ function renderAlmOption( ? almCounts[AlmKeys.BitbucketServer] + almCounts[AlmKeys.BitbucketCloud] : almCounts[alm]; const hasConfig = count > 0; - const hasTooManyConfig = count > 1; + const hasTooManyConfig = count > 1 && !ALLOWED_MULTIPLE_CONFIGS.includes(alm); const disabled = loadingBindings || hasTooManyConfig || (!hasConfig && !canAdmin); const onClick = () => { @@ -73,23 +100,10 @@ function renderAlmOption( return props.onConfigMode(alm); } - return props.onSelectMode( - isBitbucketOption && hasBitbucketCloudConf ? CreateProjectModes.BitbucketCloud : mode - ); + return props.onSelectMode(getMode(isBitbucketOption, hasBitbucketCloudConf, mode)); }; - let errorMessage = ''; - - if (hasTooManyConfig) { - errorMessage = translateWithParameters( - 'onboarding.create_project.too_many_alm_instances_X', - translate('alm', alm) - ); - } else if (!hasConfig) { - errorMessage = canAdmin - ? translate('onboarding.create_project.alm_not_configured.admin') - : translate('onboarding.create_project.alm_not_configured'); - } + const errorMessage = getErrorMessage(hasTooManyConfig, hasConfig, canAdmin, alm); return (
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 abbd5af9d05..926f2fe4f89 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 @@ -219,7 +219,7 @@ export class CreateProjectPage extends React.PureComponent { location={location} onProjectCreate={this.handleProjectCreate} router={router} - settings={gitlabSettings} + almInstances={gitlabSettings} /> ); } 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 index b51ade4a0e9..2c2c80ec3a8 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx @@ -29,7 +29,7 @@ interface Props { canAdmin: boolean; loadingBindings: boolean; onProjectCreate: (projectKey: string) => void; - settings: AlmSettingsInstance[]; + almInstances: AlmSettingsInstance[]; location: Location; router: Router; } @@ -43,7 +43,7 @@ interface State { resetPat: boolean; searching: boolean; searchQuery: string; - settings?: AlmSettingsInstance; + selectedAlmInstance: AlmSettingsInstance; showPersonalAccessTokenForm: boolean; } @@ -63,7 +63,7 @@ export default class GitlabProjectCreate extends React.PureComponent 0) { - this.setState( - { settings: this.props.settings.length === 1 ? this.props.settings[0] : undefined }, - () => this.fetchInitialData() - ); + const { almInstances } = this.props; + if (prevProps.almInstances.length === 0 && this.props.almInstances.length > 0) { + this.setState({ selectedAlmInstance: almInstances[0] }, () => this.fetchInitialData()); } } @@ -115,14 +113,14 @@ export default class GitlabProjectCreate extends React.PureComponent { - const { settings } = this.state; - if (!settings) { + const { selectedAlmInstance } = this.state; + if (!selectedAlmInstance) { return Promise.resolve(undefined); } try { return await getGitlabProjects({ - almSetting: settings.key, + almSetting: selectedAlmInstance.key, page: pageIndex, pageSize: GITLAB_PROJECTS_PAGESIZE, query, @@ -133,15 +131,15 @@ export default class GitlabProjectCreate extends React.PureComponent { - const { settings } = this.state; + const { selectedAlmInstance } = this.state; - if (!settings) { + if (!selectedAlmInstance) { return Promise.resolve(undefined); } try { return await importGitlabProject({ - almSetting: settings.key, + almSetting: selectedAlmInstance.key, gitlabProjectId, }); } catch (_) { @@ -172,7 +170,6 @@ export default class GitlabProjectCreate extends React.PureComponent ({ loadingMore: false, @@ -186,7 +183,6 @@ export default class GitlabProjectCreate extends React.PureComponent ({ searching: false, @@ -208,8 +204,17 @@ export default class GitlabProjectCreate extends React.PureComponent { + this.setState({ + selectedAlmInstance: instance, + showPersonalAccessTokenForm: true, + projects: undefined, + resetPat: false, + }); + }; + render() { - const { canAdmin, loadingBindings, location } = this.props; + const { loadingBindings, location, almInstances, canAdmin } = this.props; const { importingGitlabProjectId, loading, @@ -219,14 +224,15 @@ export default class GitlabProjectCreate extends React.PureComponent ); } 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 index 8dc36218a5a..668420dffe2 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import AlmSettingsInstanceSelector from '../../../components/devops-platform/AlmSettingsInstanceSelector'; import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; import { GitlabProject } from '../../../types/alm-integration'; @@ -42,8 +43,10 @@ export interface GitlabProjectCreateRendererProps { resetPat: boolean; searching: boolean; searchQuery: string; - settings?: AlmSettingsInstance; + almInstances?: AlmSettingsInstance[]; + selectedAlmInstance?: AlmSettingsInstance; showPersonalAccessTokenForm?: boolean; + onChangeConfig: (instance: AlmSettingsInstance) => void; } export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRendererProps) { @@ -57,7 +60,8 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe resetPat, searching, searchQuery, - settings, + selectedAlmInstance, + almInstances, showPersonalAccessTokenForm, } = props; @@ -77,17 +81,32 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe } /> + {almInstances && almInstances.length > 1 && ( +
+ + +
+ )} + {loading && } - {!loading && !settings && ( + {!loading && !selectedAlmInstance && ( )} {!loading && - settings && + selectedAlmInstance && (showPersonalAccessTokenForm ? ( 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 index 3462224c37d..d3b501776c0 100644 --- a/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx @@ -75,12 +75,26 @@ export default class PersonalAccessTokenForm extends React.PureComponent { const { almSetting: { key }, resetPat, } = this.props; - this.mounted = true; // We don't need to check PAT if we want to reset if (!resetPat) { @@ -106,11 +120,7 @@ export default class PersonalAccessTokenForm extends React.PureComponent) => { this.setState({ @@ -379,7 +389,7 @@ export default class PersonalAccessTokenForm extends React.PureComponent { + almIntegrationHandler = new AlmIntegrationsServiceMock(); + almSettingsHandler = new AlmSettingsServiceMock(); +}); + +afterEach(() => { + almIntegrationHandler.reset(); + almSettingsHandler.reset(); +}); + +describe('Gitlab onboarding page', () => { + it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { + const user = userEvent.setup(); + renderCreateProject(); + expect(ui.gitlabCreateProjectButton.get()).toBeInTheDocument(); + + await user.click(ui.gitlabCreateProjectButton.get()); + expect(screen.getByText('onboarding.create_project.gitlab.title')).toBeInTheDocument(); + expect(screen.getByText('alm.configuration.selector.label')).toBeInTheDocument(); + + expect(screen.getByText('onboarding.create_project.enter_pat')).toBeInTheDocument(); + expect(screen.getByText('onboarding.create_project.pat_help.title')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'save' })).toBeInTheDocument(); + + await user.click(ui.personalAccessTokenInput.get()); + await user.keyboard('secret'); + await user.click(screen.getByRole('button', { name: 'save' })); + + expect(screen.getByText('Gitlab project 1')).toBeInTheDocument(); + expect(screen.getByText('Gitlab project 2')).toBeInTheDocument(); + expect(screen.getAllByText('onboarding.create_project.set_up')).toHaveLength(2); + expect(screen.getByText('onboarding.create_project.repository_imported')).toBeInTheDocument(); + }); + + it('should show import project feature when PAT is already set', async () => { + const user = userEvent.setup(); + renderCreateProject(); + await act(async () => { + await user.click(ui.gitlabCreateProjectButton.get()); + await selectEvent.select(ui.instanceSelector.get(), [/conf-final-2/]); + }); + + expect(screen.getByText('Gitlab project 1')).toBeInTheDocument(); + expect(screen.getByText('Gitlab project 2')).toBeInTheDocument(); + }); + + it('should show no result message when there are no projects', async () => { + const user = userEvent.setup(); + almIntegrationHandler.setGitlabProjects([]); + renderCreateProject(); + await act(async () => { + await user.click(ui.gitlabCreateProjectButton.get()); + await selectEvent.select(ui.instanceSelector.get(), [/conf-final-2/]); + }); + + expect(screen.getByText('onboarding.create_project.gitlab.no_projects')).toBeInTheDocument(); + }); +}); + +function renderCreateProject() { + renderApp('project/create', ); +} 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 index 4504a6e0912..4293a00e936 100644 --- 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 @@ -154,7 +154,7 @@ it('should import', async () => { }); it('should do nothing with missing settings', async () => { - const wrapper = shallowRender({ settings: [] }); + const wrapper = shallowRender({ almInstances: [] }); await wrapper.instance().handleLoadMore(); await wrapper.instance().handleSearch('whatever'); @@ -204,7 +204,7 @@ function shallowRender(props: Partial = {}) { location={mockLocation()} onProjectCreate={jest.fn()} router={mockRouter()} - settings={[mockAlmSettingsInstance({ alm: AlmKeys.GitLab, key: almSettingKey })]} + almInstances={[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 index dd0773a03bf..9cdb7d6aa0e 100644 --- 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 @@ -27,8 +27,8 @@ import 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( + expect(shallowRender({ almInstances: undefined })).toMatchSnapshot('invalid settings'); + expect(shallowRender({ almInstances: undefined })).toMatchSnapshot( 'invalid settings, admin user' ); expect(shallowRender()).toMatchSnapshot('pat form'); @@ -47,13 +47,19 @@ function shallowRender(props: Partial = {}) { onLoadMore={jest.fn()} onPersonalAccessTokenCreated={jest.fn()} onSearch={jest.fn()} + onChangeConfig={jest.fn()} projects={undefined} projectsPaging={{ pageIndex: 1, pageSize: 30, total: 0 }} searching={false} searchQuery="" resetPat={false} showPersonalAccessTokenForm={true} - settings={mockAlmSettingsInstance({ alm: AlmKeys.GitLab })} + almInstances={[ + mockAlmSettingsInstance({ alm: AlmKeys.GitLab }), + mockAlmSettingsInstance({ alm: AlmKeys.GitLab }), + mockAlmSettingsInstance({ alm: AlmKeys.GitHub }), + ]} + selectedAlmInstance={mockAlmSettingsInstance({ alm: AlmKeys.GitLab })} {...props} /> ); 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 index 51f82e784c5..5ab0fec34d5 100644 --- 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 @@ -97,7 +97,7 @@ it('should correctly handle form for bitbucket interactions', async () => { // Submit button disabled by default. expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); - change(wrapper.find('#personal_access_token'), 'token'); + change(wrapper.find('input#personal_access_token_validation'), 'token'); expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true); // Submit button enabled if there's a value. @@ -120,7 +120,7 @@ it('should show error when issue', async () => { (checkPersonalAccessTokenIsValid as jest.Mock).mockRejectedValueOnce({}); - change(wrapper.find('#personal_access_token'), 'token'); + change(wrapper.find('input#personal_access_token_validation'), 'token'); change(wrapper.find('#username'), 'username'); // Expect correct calls to be made when submitting. 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 1d7755f6e7a..7d5f76c3837 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 @@ -296,6 +296,7 @@ exports[`should render correctly for gitlab mode 1`] = ` id="create-project" >
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 index 577f859ef13..e6a39694a60 100644 --- 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 @@ -2,9 +2,18 @@ exports[`should render correctly 1`] = ` } /> - `; @@ -41,9 +47,15 @@ exports[`should render correctly: invalid settings, admin user 1`] = ` } /> - `; @@ -65,6 +77,38 @@ exports[`should render correctly: loading 1`] = ` } /> +
+ + +
@@ -88,6 +132,38 @@ exports[`should render correctly: pat form 1`] = ` } /> +
+ + +
} /> +
+ + +
{ currentAlmSettings = almSettings.filter((s) => s.alm === key); } return ( - currentAlmSettings.length === 1 && + this.configLengthChecker(key, currentAlmSettings.length) && key === currentAlmSettings[0].alm && this.almSettingIsValid(currentAlmSettings[0]) ); @@ -103,6 +104,10 @@ export class ProjectCreationMenu extends React.PureComponent { } }; + configLengthChecker = (key: AlmKeys, length: number) => { + return ALLOWED_MULTIPLE_CONFIGS.includes(key) ? length > 0 : length === 1; + }; + render() { const { className, currentUser } = this.props; const { boundAlms } = this.state; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx index e3f2908bf9b..fabc60c1c97 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx @@ -121,7 +121,7 @@ it('should filter alm bindings appropriately', async () => { wrapper = shallowRender(); await waitAndUpdate(wrapper); - expect(wrapper.state().boundAlms).toEqual([]); + expect(wrapper.state().boundAlms).toEqual([AlmKeys.GitLab]); }); function shallowRender(overrides: Partial = {}) { diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx index 7a5b9780acf..ee27acc7694 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx @@ -38,6 +38,7 @@ import { AlmSettingsBindingStatusType, } from '../../../../types/alm-settings'; import { EditionKey } from '../../../../types/editions'; +import { ALLOWED_MULTIPLE_CONFIGS } from '../../../create/project/constants'; export interface AlmBindingDefinitionBoxProps { alm: AlmKeys; @@ -110,7 +111,7 @@ function getImportFeatureStatus( multipleDefinitions: boolean, type: AlmSettingsBindingStatusType.Success | AlmSettingsBindingStatusType.Failure ) { - if (multipleDefinitions) { + if (multipleDefinitions && !ALLOWED_MULTIPLE_CONFIGS.includes(alm)) { return (
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx index 5589c1e9527..c0737d418eb 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx @@ -19,10 +19,9 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import { components, OptionProps, SingleValueProps } from 'react-select'; import Link from '../../../../components/common/Link'; import { Button, SubmitButton } from '../../../../components/controls/buttons'; -import Select from '../../../../components/controls/Select'; +import AlmSettingsInstanceSelector from '../../../../components/devops-platform/AlmSettingsInstanceSelector'; import AlertSuccessIcon from '../../../../components/icons/AlertSuccessIcon'; import { Alert } from '../../../../components/ui/Alert'; import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; @@ -57,25 +56,6 @@ export interface PRDecorationBindingRendererProps { isSysAdmin: boolean; } -function optionRenderer(props: OptionProps) { - return {customOptions(props.data)}; -} - -function singleValueRenderer(props: SingleValueProps) { - return {customOptions(props.data)}; -} - -function customOptions(instance: AlmSettingsInstance) { - return instance.url ? ( - <> - {instance.key} — - {instance.url} - - ) : ( - {instance.key} - ); -} - export default function PRDecorationBindingRenderer(props: PRDecorationBindingRendererProps) { const { formData, @@ -151,18 +131,12 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe
-
@@ -329,19 +313,11 @@ exports[`should render correctly: when there are configuration errors (admin use
-
@@ -608,19 +576,11 @@ exports[`should render correctly: with a single ALM instance 1`] = `
-
@@ -848,19 +792,11 @@ exports[`should render correctly: with an empty form 1`] = `
- { + if (inst) { + props.onChange(inst); + } + }} + components={{ + Option: optionRenderer, + SingleValue: singleValueRenderer, + }} + getOptionValue={(opt) => opt.key} + value={instances.find((inst) => inst.key === initialValue)} + /> + ); +} 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 ae40c64092d..aa861ca230e 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -389,6 +389,7 @@ alm.github=GitHub alm.github.short=GitHub alm.gitlab=GitLab alm.gitlab.short=GitLab +alm.configuration.selector.label=What DevOps platform do you want to import project from? #------------------------------------------------------------------------------ # @@ -3598,7 +3599,7 @@ onboarding.create_project.github.warning.message_admin=Please make sure the GitH onboarding.create_project.github.warning.message_admin.link=DevOps Platform 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 set up? +onboarding.create_project.gitlab.title=Gitlab project onboarding onboarding.create_project.gitlab.no_projects=No projects could be fetched from Gitlab. Contact your system administrator, or {link}. onboarding.create_project.gitlab.link=See on GitLab