From 7fe3240e62bb99fe1c64ea469689ccd668af0fa6 Mon Sep 17 00:00:00 2001 From: Revanshu Paliwal Date: Thu, 17 Nov 2022 12:59:09 +0100 Subject: [PATCH] SONAR-17587 Allow project onboarding when multiple Github integrations are configured --- .../api/mocks/AlmIntegrationsServiceMock.ts | 28 ++++ .../js/api/mocks/AlmSettingsServiceMock.ts | 2 + .../create/project/AzureProjectCreate.tsx | 4 +- .../project/AzureProjectCreateRenderer.tsx | 4 +- .../apps/create/project/CreateProjectPage.tsx | 6 +- .../create/project/GitHubProjectCreate.tsx | 90 ++++++++---- .../project/GitHubProjectCreateRenderer.tsx | 19 ++- .../create/project/GitlabProjectCreate.tsx | 4 +- .../project/GitlabProjectCreateRenderer.tsx | 4 +- .../AzureProjectCreateRenderer-test.tsx | 2 +- .../project/__tests__/CreateProject-it.tsx | 39 ++++- .../__tests__/GitHubProjectCreate-test.tsx | 8 +- .../GitHubProjectCreateRenderer-test.tsx | 3 + .../GitlabProjectCreateRenderer-test.tsx | 2 +- .../AzureProjectCreate-test.tsx.snap | 2 +- .../CreateProjectModeSelection-test.tsx.snap | 42 +----- .../CreateProjectPage-test.tsx.snap | 2 +- .../GitHubProjectCreateRenderer-test.tsx.snap | 138 ++++++++++++------ .../GitlabProjectCreate-test.tsx.snap | 2 +- .../main/js/apps/create/project/constants.ts | 2 +- .../__tests__/ProjectCreationMenu-test.tsx | 2 +- .../AlmSettingsInstanceSelector.tsx | 2 + .../resources/org/sonar/l10n/core.properties | 5 +- 23 files changed, 268 insertions(+), 144 deletions(-) diff --git a/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts index d63bbf29a17..337a739873b 100644 --- a/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts @@ -22,6 +22,8 @@ import { mockGitlabProject } from '../../helpers/mocks/alm-integrations'; import { GitlabProject } from '../../types/alm-integration'; import { checkPersonalAccessTokenIsValid, + getGithubClientId, + getGithubOrganizations, getGitlabProjects, setAlmPersonalAccessToken, } from '../alm-integrations'; @@ -32,6 +34,8 @@ export default class AlmIntegrationsServiceMock { defaultAlmInstancePATMap: { [key: string]: boolean } = { 'conf-final-1': false, 'conf-final-2': true, + 'conf-github-1': false, + 'conf-github-2': true, }; defaultGitlabProjects: GitlabProject[] = [ @@ -45,6 +49,20 @@ export default class AlmIntegrationsServiceMock { mockGitlabProject({ name: 'Gitlab project 3', id: '3' }), ]; + defaultOrganizations = { + paging: { + pageIndex: 1, + pageSize: 100, + total: 1, + }, + organizations: [ + { + key: 'org-1', + name: 'org-1', + }, + ], + }; + constructor() { this.almInstancePATMap = cloneDeep(this.defaultAlmInstancePATMap); this.gitlabProjects = cloneDeep(this.defaultGitlabProjects); @@ -53,6 +71,8 @@ export default class AlmIntegrationsServiceMock { ); (setAlmPersonalAccessToken as jest.Mock).mockImplementation(this.setAlmPersonalAccessToken); (getGitlabProjects as jest.Mock).mockImplementation(this.getGitlabProjects); + (getGithubClientId as jest.Mock).mockImplementation(this.getGithubClientId); + (getGithubOrganizations as jest.Mock).mockImplementation(this.getGithubOrganizations); } checkPersonalAccessTokenIsValid = (conf: string) => { @@ -79,6 +99,14 @@ export default class AlmIntegrationsServiceMock { this.gitlabProjects = gitlabProjects; } + getGithubClientId = () => { + return Promise.resolve({ clientId: 'clientId' }); + }; + + getGithubOrganizations = () => { + return Promise.resolve(this.defaultOrganizations); + }; + 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 index 9fb8b1bc6b2..d42e24c6570 100644 --- a/server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts @@ -27,6 +27,8 @@ export default class AlmSettingsServiceMock { defaultSetting: AlmSettingsInstance[] = [ mockAlmSettingsInstance({ key: 'conf-final-1', alm: AlmKeys.GitLab }), mockAlmSettingsInstance({ key: 'conf-final-2', alm: AlmKeys.GitLab }), + mockAlmSettingsInstance({ key: 'conf-github-1', alm: AlmKeys.GitHub, url: 'url' }), + mockAlmSettingsInstance({ key: 'conf-github-2', alm: AlmKeys.GitHub, url: 'url' }), ]; constructor() { diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx index 1c9864fd9e4..2463f612235 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx @@ -273,7 +273,7 @@ export default class AzureProjectCreate extends React.PureComponent { + onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => { this.setState({ selectedAlmInstance: instance }, () => this.fetchData()); }; @@ -317,7 +317,7 @@ export default class AzureProjectCreate extends React.PureComponent ); } diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx index 22fa07d7101..7bd6e4f0902 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx @@ -58,7 +58,7 @@ export interface AzureProjectCreateRendererProps { showPersonalAccessTokenForm?: boolean; submittingToken?: boolean; tokenValidationFailed: boolean; - onChangeConfig: (instance: AlmSettingsInstance) => void; + onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void; } export default function AzureProjectCreateRenderer(props: AzureProjectCreateRendererProps) { @@ -118,7 +118,7 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend {loading && } 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 21100831bd3..aa6f2ff06b6 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 @@ -42,7 +42,7 @@ import ManualProjectCreate from './ManualProjectCreate'; import './style.css'; import { CreateProjectModes } from './types'; -interface Props extends WithAvailableFeaturesProps { +export interface CreateProjectPageProps extends WithAvailableFeaturesProps { appState: AppState; location: Location; router: Router; @@ -66,7 +66,7 @@ const PROJECT_MODE_FOR_ALM_KEY = { [AlmKeys.GitLab]: CreateProjectModes.GitLab, }; -export class CreateProjectPage extends React.PureComponent { +export class CreateProjectPage extends React.PureComponent { mounted = false; state: State = { azureSettings: [], @@ -207,7 +207,7 @@ export class CreateProjectPage extends React.PureComponent { location={location} onProjectCreate={this.handleProjectCreate} router={router} - settings={githubSettings} + almInstances={githubSettings} /> ); } diff --git a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx index bb009226dc3..cefce5099ed 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx @@ -36,7 +36,7 @@ interface Props { canAdmin: boolean; loadingBindings: boolean; onProjectCreate: (projectKey: string) => void; - settings: AlmSettingsInstance[]; + almInstances: AlmSettingsInstance[]; location: Location; router: Router; } @@ -52,7 +52,7 @@ interface State { searchQuery: string; selectedOrganization?: GithubOrganization; selectedRepository?: GithubRepository; - settings?: AlmSettingsInstance; + selectedAlmInstance?: AlmSettingsInstance; } const REPOSITORY_PAGE_SIZE = 30; @@ -72,7 +72,7 @@ export default class GitHubProjectCreate extends React.Component { repositories: [], repositoryPaging: { pageSize: REPOSITORY_PAGE_SIZE, total: 0, pageIndex: 1 }, searchQuery: '', - settings: props.settings[0], + selectedAlmInstance: this.getInitialSelectedAlmInstance(), }; this.triggerSearch = debounce(this.triggerSearch, 250); @@ -80,13 +80,14 @@ export default class GitHubProjectCreate extends React.Component { componentDidMount() { this.mounted = true; - this.initialize(); } componentDidUpdate(prevProps: Props) { - if (prevProps.settings.length === 0 && this.props.settings.length > 0) { - this.setState({ settings: this.props.settings[0] }, () => this.initialize()); + if (prevProps.almInstances.length === 0 && this.props.almInstances.length > 0) { + this.setState({ selectedAlmInstance: this.getInitialSelectedAlmInstance() }, () => + this.initialize() + ); } } @@ -94,26 +95,39 @@ export default class GitHubProjectCreate extends React.Component { this.mounted = false; } + getInitialSelectedAlmInstance() { + const { + location: { + query: { almInstance: selectedAlmInstanceKey }, + }, + almInstances, + } = this.props; + const selectedAlmInstance = almInstances.find( + (instance) => instance.key === selectedAlmInstanceKey + ); + if (selectedAlmInstance) { + return selectedAlmInstance; + } + return this.props.almInstances.length > 1 ? undefined : this.props.almInstances[0]; + } + async initialize() { const { location, router } = this.props; - const { settings } = this.state; - - if (!settings || !settings.url) { + const { selectedAlmInstance } = this.state; + if (!selectedAlmInstance || !selectedAlmInstance.url) { this.setState({ error: true }); return; - } else { - this.setState({ error: false }); } + this.setState({ error: false }); const code = location.query?.code; - try { if (!code) { - await this.redirectToGithub(settings); + await this.redirectToGithub(selectedAlmInstance); } else { delete location.query.code; router.replace(location); - await this.fetchOrganizations(settings, code); + await this.fetchOrganizations(selectedAlmInstance, code); } } catch (e) { if (this.mounted) { @@ -122,33 +136,39 @@ export default class GitHubProjectCreate extends React.Component { } } - async redirectToGithub(settings: AlmSettingsInstance) { - if (!settings.url) { + async redirectToGithub(selectedAlmInstance: AlmSettingsInstance) { + if (!selectedAlmInstance.url) { return; } - const { clientId } = await getGithubClientId(settings.key); + const { clientId } = await getGithubClientId(selectedAlmInstance.key); if (!clientId) { this.setState({ error: true }); return; } - const queryParams = [ { param: 'client_id', value: clientId }, - { param: 'redirect_uri', value: `${getHostUrl()}/projects/create?mode=${AlmKeys.GitHub}` }, + { + param: 'redirect_uri', + value: encodeURIComponent( + `${getHostUrl()}/projects/create?mode=${AlmKeys.GitHub}&almInstance=${ + selectedAlmInstance.key + }` + ), + }, ] .map(({ param, value }) => `${param}=${value}`) .join('&'); let instanceRootUrl; // Strip the api section from the url, since we're not hitting the api here. - if (settings.url.includes('/api/v3')) { + if (selectedAlmInstance.url.includes('/api/v3')) { // GitHub Enterprise - instanceRootUrl = settings.url.replace('/api/v3', ''); + instanceRootUrl = selectedAlmInstance.url.replace('/api/v3', ''); } else { // github.com - instanceRootUrl = settings.url.replace('api.', ''); + instanceRootUrl = selectedAlmInstance.url.replace('api.', ''); } // strip the trailing / @@ -156,8 +176,8 @@ export default class GitHubProjectCreate extends React.Component { window.location.replace(`${instanceRootUrl}/login/oauth/authorize?${queryParams}`); } - async fetchOrganizations(settings: AlmSettingsInstance, token: string) { - const { organizations } = await getGithubOrganizations(settings.key, token); + async fetchOrganizations(selectedAlmInstance: AlmSettingsInstance, token: string) { + const { organizations } = await getGithubOrganizations(selectedAlmInstance.key, token); if (this.mounted) { this.setState({ loadingOrganizations: false, organizations }); @@ -166,9 +186,9 @@ export default class GitHubProjectCreate extends React.Component { async fetchRepositories(params: { organizationKey: string; page?: number; query?: string }) { const { organizationKey, page = 1, query } = params; - const { settings } = this.state; + const { selectedAlmInstance } = this.state; - if (!settings) { + if (!selectedAlmInstance) { this.setState({ error: true }); return; } @@ -177,7 +197,7 @@ export default class GitHubProjectCreate extends React.Component { try { const data = await getGithubRepositories({ - almSetting: settings.key, + almSetting: selectedAlmInstance.key, organization: organizationKey, pageSize: REPOSITORY_PAGE_SIZE, page, @@ -243,14 +263,14 @@ export default class GitHubProjectCreate extends React.Component { }; handleImportRepository = async () => { - const { selectedOrganization, selectedRepository, settings } = this.state; + const { selectedOrganization, selectedRepository, selectedAlmInstance } = this.state; - if (settings && selectedOrganization && selectedRepository) { + if (selectedAlmInstance && selectedOrganization && selectedRepository) { this.setState({ importing: true }); try { const { project } = await importGithubRepository( - settings.key, + selectedAlmInstance.key, selectedOrganization.key, selectedRepository.key ); @@ -264,8 +284,12 @@ export default class GitHubProjectCreate extends React.Component { } }; + onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => { + this.setState({ selectedAlmInstance: instance }, () => this.initialize()); + }; + render() { - const { canAdmin, loadingBindings } = this.props; + const { canAdmin, loadingBindings, almInstances } = this.props; const { error, importing, @@ -277,6 +301,7 @@ export default class GitHubProjectCreate extends React.Component { searchQuery, selectedOrganization, selectedRepository, + selectedAlmInstance, } = this.state; return ( @@ -298,6 +323,9 @@ export default class GitHubProjectCreate extends React.Component { repositories={repositories} selectedOrganization={selectedOrganization} selectedRepository={selectedRepository} + almInstances={almInstances} + selectedAlmInstance={selectedAlmInstance} + onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange} /> ); } diff --git a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx index 1d10ee93751..7142fa277d5 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx @@ -37,8 +37,10 @@ import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; import { getProjectUrl } from '../../../helpers/urls'; import { GithubOrganization, GithubRepository } from '../../../types/alm-integration'; +import { AlmSettingsInstance } from '../../../types/alm-settings'; import { ComponentQualifier } from '../../../types/component'; import { Paging } from '../../../types/types'; +import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown'; import CreateProjectPageHeader from './CreateProjectPageHeader'; export interface GitHubProjectCreateRendererProps { @@ -59,6 +61,9 @@ export interface GitHubProjectCreateRendererProps { searchQuery: string; selectedOrganization?: GithubOrganization; selectedRepository?: GithubRepository; + almInstances: AlmSettingsInstance[]; + selectedAlmInstance?: AlmSettingsInstance; + onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void; } function orgToOption({ key, name }: GithubOrganization) { @@ -176,6 +181,8 @@ export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRe organizations, selectedOrganization, selectedRepository, + almInstances, + selectedAlmInstance, } = props; if (loadingBindings) { @@ -212,7 +219,13 @@ export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRe } /> - {error ? ( + + + {error && selectedAlmInstance && (

@@ -239,7 +252,9 @@ export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRe

- ) : ( + )} + + {!error && (
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 2c2c80ec3a8..d252ded7142 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 @@ -204,7 +204,7 @@ export default class GitlabProjectCreate extends React.PureComponent { + onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => { this.setState({ selectedAlmInstance: instance, showPersonalAccessTokenForm: true, @@ -248,7 +248,7 @@ 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 a90b987b31a..9078dc40730 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 @@ -46,7 +46,7 @@ export interface GitlabProjectCreateRendererProps { almInstances?: AlmSettingsInstance[]; selectedAlmInstance?: AlmSettingsInstance; showPersonalAccessTokenForm?: boolean; - onChangeConfig: (instance: AlmSettingsInstance) => void; + onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void; } export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRendererProps) { @@ -84,7 +84,7 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe {loading && } diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx index c4549235f11..a5b7f00bde1 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx @@ -83,7 +83,7 @@ function shallowRender(overrides: Partial = {}) ]} showPersonalAccessTokenForm={false} submittingToken={false} - onChangeConfig={jest.fn()} + onSelectedAlmInstanceChange={jest.fn()} {...overrides} /> ); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProject-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProject-it.tsx index ca3c8f2e6b0..0f8954c3eca 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProject-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProject-it.tsx @@ -25,16 +25,19 @@ import { byLabelText, byRole, byText } from 'testing-library-selector'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; import { renderApp } from '../../../../helpers/testReactTestingUtils'; -import CreateProjectPage from '../CreateProjectPage'; +import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage'; jest.mock('../../../../api/alm-integrations'); jest.mock('../../../../api/alm-settings'); +const original = window.location; + let almIntegrationHandler: AlmIntegrationsServiceMock; let almSettingsHandler: AlmSettingsServiceMock; const ui = { gitlabCreateProjectButton: byText('onboarding.create_project.select_method.gitlab'), + githubCreateProjectButton: byText('onboarding.create_project.select_method.github'), personalAccessTokenInput: byRole('textbox', { name: 'onboarding.create_project.enter_pat field_required', }), @@ -42,6 +45,10 @@ const ui = { }; beforeAll(() => { + Object.defineProperty(window, 'location', { + configurable: true, + value: { replace: jest.fn() }, + }); almIntegrationHandler = new AlmIntegrationsServiceMock(); almSettingsHandler = new AlmSettingsServiceMock(); }); @@ -51,6 +58,10 @@ afterEach(() => { almSettingsHandler.reset(); }); +afterAll(() => { + Object.defineProperty(window, 'location', { configurable: true, value: original }); +}); + 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(); @@ -100,6 +111,28 @@ describe('Gitlab onboarding page', () => { }); }); -function renderCreateProject() { - renderApp('project/create', ); +describe('Github onboarding page', () => { + it('should redirect to github authorization page when not already authorized', async () => { + const user = userEvent.setup(); + renderCreateProject(); + expect(ui.githubCreateProjectButton.get()).toBeInTheDocument(); + + await user.click(ui.githubCreateProjectButton.get()); + expect(screen.getByText('onboarding.create_project.github.title')).toBeInTheDocument(); + expect(screen.getByText('alm.configuration.selector.placeholder')).toBeInTheDocument(); + expect(screen.getByText('alm.configuration.selector.label')).toBeInTheDocument(); + + await selectEvent.select(screen.getByLabelText('alm.configuration.selector.label'), [ + /conf-github-1/, + ]); + + expect(window.location.replace).toHaveBeenCalled(); + expect( + screen.getByText('onboarding.create_project.github.choose_organization') + ).toBeInTheDocument(); + }); +}); + +function renderCreateProject(props: Partial = {}) { + renderApp('project/create', ); } diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx index f17a178b463..b3fd5f8908e 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx @@ -63,7 +63,7 @@ beforeEach(() => { }); it('should handle no settings', async () => { - const wrapper = shallowRender({ settings: [] }); + const wrapper = shallowRender({ almInstances: [] }); await waitAndUpdate(wrapper); expect(wrapper.state().error).toBe(true); }); @@ -78,13 +78,13 @@ it('should redirect when no code', async () => { it('should redirect when no code - github.com', async () => { const wrapper = shallowRender({ - settings: [mockAlmSettingsInstance({ key: 'a', url: 'api.github.com' })], + almInstances: [mockAlmSettingsInstance({ key: 'a', url: 'api.github.com' })], }); await waitAndUpdate(wrapper); expect(getGithubClientId).toHaveBeenCalled(); expect(window.location.replace).toHaveBeenCalledWith( - 'github.com/login/oauth/authorize?client_id=client-id-124&redirect_uri=http://localhost/projects/create?mode=github' + 'github.com/login/oauth/authorize?client_id=client-id-124&redirect_uri=http%3A%2F%2Flocalhost%2Fprojects%2Fcreate%3Fmode%3Dgithub%26almInstance%3Da' ); }); @@ -239,7 +239,7 @@ function shallowRender(props: Partial = {}) { location={mockLocation()} onProjectCreate={jest.fn()} router={mockRouter()} - settings={[mockAlmSettingsInstance({ key: 'a', url: 'geh.company.com/api/v3' })]} + almInstances={[mockAlmSettingsInstance({ key: 'a', url: 'geh.company.com/api/v3' })]} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreateRenderer-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreateRenderer-test.tsx index 05c57f0b2aa..7a249c19173 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreateRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreateRenderer-test.tsx @@ -23,6 +23,7 @@ import Radio from '../../../../components/controls/Radio'; import SearchBox from '../../../../components/controls/SearchBox'; import Select from '../../../../components/controls/Select'; import { mockGitHubRepository } from '../../../../helpers/mocks/alm-integrations'; +import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; import { GithubOrganization } from '../../../../types/alm-integration'; import GitHubProjectCreateRenderer, { GitHubProjectCreateRendererProps, @@ -116,6 +117,8 @@ function shallowRender(props: Partial = {}) { onSearch={jest.fn()} onSelectOrganization={jest.fn()} onSelectRepository={jest.fn()} + onSelectedAlmInstanceChange={jest.fn()} + almInstances={[mockAlmSettingsInstance(), mockAlmSettingsInstance()]} organizations={[]} repositoryPaging={{ total: 0, pageIndex: 1, pageSize: 30 }} searchQuery="" 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 9cdb7d6aa0e..3422a875696 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 @@ -47,7 +47,7 @@ function shallowRender(props: Partial = {}) { onLoadMore={jest.fn()} onPersonalAccessTokenCreated={jest.fn()} onSearch={jest.fn()} - onChangeConfig={jest.fn()} + onSelectedAlmInstanceChange={jest.fn()} projects={undefined} projectsPaging={{ pageIndex: 1, pageSize: 30, total: 0 }} searching={false} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap index 4af7d5f4433..eb3a7ab08aa 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap @@ -14,12 +14,12 @@ exports[`should render correctly 1`] = ` importing={false} loading={true} loadingRepositories={Object {}} - onChangeConfig={[Function]} onImportRepository={[Function]} onOpenProject={[Function]} onPersonalAccessTokenCreate={[Function]} onSearch={[Function]} onSelectRepository={[Function]} + onSelectedAlmInstanceChange={[Function]} repositories={Object {}} selectedAlmInstance={ Object { diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap index 6b8276ef225..2d6c27f9ac7 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap @@ -189,8 +189,8 @@ exports[`should render correctly: invalid configs, admin 1`] = ` className="display-flex-column" >
-

- onboarding.create_project.too_many_alm_instances_X.alm.github -

-

- onboarding.create_project.too_many_alm_instances_X.alm.github -

-

- onboarding.create_project.too_many_alm_instances_X.alm.github -

diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap index c47fef67a68..186284d265b 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap @@ -17,6 +17,21 @@ exports[`should render correctly: default 1`] = ` } /> + @@ -54,24 +69,21 @@ exports[`should render correctly: error 1`] = ` } /> -
-
-

- onboarding.create_project.github.warning.title -

- - onboarding.create_project.github.warning.message - -
-
+ `; @@ -92,36 +104,21 @@ exports[`should render correctly: error for admin 1`] = ` } /> -
-
-

- onboarding.create_project.github.warning.title -

- - - onboarding.create_project.github.warning.message_admin.link - , - } - } - /> - -
-
+ `; @@ -161,6 +158,21 @@ exports[`should render correctly: no repositories 1`] = ` } /> + @@ -214,6 +226,21 @@ exports[`should render correctly: organizations 1`] = ` } /> + @@ -279,6 +306,21 @@ exports[`should render correctly: repositories 1`] = ` } /> + 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 e6a39694a60..1fe927da52e 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 @@ -13,11 +13,11 @@ exports[`should render correctly 1`] = ` canAdmin={false} loading={false} loadingMore={false} - onChangeConfig={[Function]} onImport={[Function]} onLoadMore={[Function]} onPersonalAccessTokenCreated={[Function]} onSearch={[Function]} + onSelectedAlmInstanceChange={[Function]} projectsPaging={ Object { "pageIndex": 1, diff --git a/server/sonar-web/src/main/js/apps/create/project/constants.ts b/server/sonar-web/src/main/js/apps/create/project/constants.ts index a620bcb52a6..68567ef7eea 100644 --- a/server/sonar-web/src/main/js/apps/create/project/constants.ts +++ b/server/sonar-web/src/main/js/apps/create/project/constants.ts @@ -23,4 +23,4 @@ export const PROJECT_NAME_MAX_LEN = 255; export const DEFAULT_BBS_PAGE_SIZE = 25; -export const ALLOWED_MULTIPLE_CONFIGS = [AlmKeys.GitLab, AlmKeys.Azure]; +export const ALLOWED_MULTIPLE_CONFIGS = [AlmKeys.GitLab, AlmKeys.Azure, AlmKeys.GitHub]; 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 d53f11dff02..a73c1ea5999 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([AlmKeys.Azure, AlmKeys.GitLab]); + expect(wrapper.state().boundAlms).toEqual([AlmKeys.Azure, AlmKeys.GitHub, AlmKeys.GitLab]); }); function shallowRender(overrides: Partial = {}) { diff --git a/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx b/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx index dee6fae90a8..a6df29ddf20 100644 --- a/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx +++ b/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import { components, OptionProps, SingleValueProps } from 'react-select'; +import { translate } from '../../helpers/l10n'; import { AlmSettingsInstance } from '../../types/alm-settings'; import Select from '../controls/Select'; @@ -68,6 +69,7 @@ export default function AlmSettingsInstanceSelector(props: Props) { Option: optionRenderer, SingleValue: singleValueRenderer, }} + placeholder={translate('alm.configuration.selector.placeholder')} 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 870ff352a71..411dd234743 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -389,7 +389,8 @@ 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? +alm.configuration.selector.label=What DevOps platform configuration do you want to import projects from? +alm.configuration.selector.placeholder=Select a configuration #------------------------------------------------------------------------------ # @@ -3591,7 +3592,7 @@ onboarding.create_project.azure.no_results=No repositories match your search que onboarding.create_project.bitbucketcloud.title=Which Bitbucket Cloud repository do you want to set up? onboarding.create_project.bitbucketcloud.no_projects=No projects could be fetched from Bitbucket. Contact your system administrator, or {link}. onboarding.create_project.bitbucketcloud.link=See on Bitbucket -onboarding.create_project.github.title=Which GitHub repository do you want to set up? +onboarding.create_project.github.title=Github project onboarding onboarding.create_project.github.choose_organization=Choose organization onboarding.create_project.github.warning.title=Could not connect to GitHub onboarding.create_project.github.warning.message=Please contact an administrator to configure GitHub integration. -- 2.39.5