aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>2022-11-17 12:59:09 +0100
committersonartech <sonartech@sonarsource.com>2022-11-18 20:02:50 +0000
commit7fe3240e62bb99fe1c64ea469689ccd668af0fa6 (patch)
tree6b42965e1e4eb346cbc795d1db750db7d6f742ae /server/sonar-web
parent2f73486a9520011bb31ca17c7c992766e822e682 (diff)
downloadsonarqube-7fe3240e62bb99fe1c64ea469689ccd668af0fa6.tar.gz
sonarqube-7fe3240e62bb99fe1c64ea469689ccd668af0fa6.zip
SONAR-17587 Allow project onboarding when multiple Github integrations are configured
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts28
-rw-r--r--server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx90
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProject-it.tsx39
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreateRenderer-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap42
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap138
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/constants.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx2
22 files changed, 265 insertions, 142 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<Props, State
}
};
- onChangeConfig = (instance: AlmSettingsInstance) => {
+ onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => {
this.setState({ selectedAlmInstance: instance }, () => this.fetchData());
};
@@ -317,7 +317,7 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
showPersonalAccessTokenForm={!patIsValid || Boolean(location.query.resetPat)}
submittingToken={submittingToken}
tokenValidationFailed={tokenValidationFailed}
- onChangeConfig={this.onChangeConfig}
+ onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange}
/>
);
}
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
<AlmSettingsInstanceDropdown
almInstances={almInstances}
selectedAlmInstance={selectedAlmInstance}
- onChangeConfig={props.onChangeConfig}
+ onChangeConfig={props.onSelectedAlmInstanceChange}
/>
{loading && <i className="spinner" />}
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<Props, State> {
+export class CreateProjectPage extends React.PureComponent<CreateProjectPageProps, State> {
mounted = false;
state: State = {
azureSettings: [],
@@ -207,7 +207,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> {
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<Props, State> {
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<Props, State> {
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<Props, State> {
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<Props, State> {
}
}
- 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<Props, State> {
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<Props, State> {
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<Props, State> {
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<Props, State> {
};
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<Props, State> {
}
};
+ 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<Props, State> {
searchQuery,
selectedOrganization,
selectedRepository,
+ selectedAlmInstance,
} = this.state;
return (
@@ -298,6 +323,9 @@ export default class GitHubProjectCreate extends React.Component<Props, State> {
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 ? (
+ <AlmSettingsInstanceDropdown
+ almInstances={almInstances}
+ selectedAlmInstance={selectedAlmInstance}
+ onChangeConfig={props.onSelectedAlmInstanceChange}
+ />
+
+ {error && selectedAlmInstance && (
<div className="display-flex-justify-center">
<div className="boxed-group padded width-50 huge-spacer-top">
<h2 className="big-spacer-bottom">
@@ -239,7 +252,9 @@ export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRe
</Alert>
</div>
</div>
- ) : (
+ )}
+
+ {!error && (
<DeferredSpinner loading={loadingOrganizations}>
<div className="form-field">
<label>{translate('onboarding.create_project.github.choose_organization')}</label>
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<Props, Stat
await this.fetchInitialData();
};
- onChangeConfig = (instance: AlmSettingsInstance) => {
+ onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => {
this.setState({
selectedAlmInstance: instance,
showPersonalAccessTokenForm: true,
@@ -248,7 +248,7 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat
showPersonalAccessTokenForm={
showPersonalAccessTokenForm || Boolean(location.query.resetPat)
}
- onChangeConfig={this.onChangeConfig}
+ onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange}
/>
);
}
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
<AlmSettingsInstanceDropdown
almInstances={almInstances}
selectedAlmInstance={selectedAlmInstance}
- onChangeConfig={props.onChangeConfig}
+ onChangeConfig={props.onSelectedAlmInstanceChange}
/>
{loading && <i className="spinner" />}
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<AzureProjectCreateRendererProps> = {})
]}
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', <CreateProjectPage />);
+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<CreateProjectPageProps> = {}) {
+ renderApp('project/create', <CreateProjectPage {...props} />);
}
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<GitHubProjectCreate['props']> = {}) {
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<GitHubProjectCreateRendererProps> = {}) {
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<GitlabProjectCreateRendererProps> = {}) {
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"
>
<button
- className="button button-huge display-flex-column create-project-mode-type-alm disabled big-spacer-right"
- disabled={true}
+ className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right"
+ disabled={false}
onClick={[Function]}
type="button"
>
@@ -204,16 +204,6 @@ exports[`should render correctly: invalid configs, admin 1`] = `
>
onboarding.create_project.select_method.github
</div>
- <p
- className="text-muted small spacer-top"
- style={
- Object {
- "lineHeight": 1.5,
- }
- }
- >
- onboarding.create_project.too_many_alm_instances_X.alm.github
- </p>
</button>
</div>
<div
@@ -329,8 +319,8 @@ exports[`should render correctly: invalid configs, admin 2`] = `
className="display-flex-column"
>
<button
- className="button button-huge display-flex-column create-project-mode-type-alm disabled big-spacer-right"
- disabled={true}
+ className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right"
+ disabled={false}
onClick={[Function]}
type="button"
>
@@ -344,16 +334,6 @@ exports[`should render correctly: invalid configs, admin 2`] = `
>
onboarding.create_project.select_method.github
</div>
- <p
- className="text-muted small spacer-top"
- style={
- Object {
- "lineHeight": 1.5,
- }
- }
- >
- onboarding.create_project.too_many_alm_instances_X.alm.github
- </p>
</button>
</div>
<div
@@ -469,8 +449,8 @@ exports[`should render correctly: invalid configs, not admin 1`] = `
className="display-flex-column"
>
<button
- className="button button-huge display-flex-column create-project-mode-type-alm disabled big-spacer-right"
- disabled={true}
+ className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right"
+ disabled={false}
onClick={[Function]}
type="button"
>
@@ -484,16 +464,6 @@ exports[`should render correctly: invalid configs, not admin 1`] = `
>
onboarding.create_project.select_method.github
</div>
- <p
- className="text-muted small spacer-top"
- style={
- Object {
- "lineHeight": 1.5,
- }
- }
- >
- onboarding.create_project.too_many_alm_instances_X.alm.github
- </p>
</button>
</div>
<div
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 a49fc0433bb..623e4d2b0dc 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
@@ -245,6 +245,7 @@ exports[`should render correctly for github mode 1`] = `
id="create-project"
>
<GitHubProjectCreate
+ almInstances={Array []}
canAdmin={false}
loadingBindings={true}
location={
@@ -273,7 +274,6 @@ exports[`should render correctly for github mode 1`] = `
"setRouteLeaveHook": [MockFunction],
}
}
- settings={Array []}
/>
</div>
</Fragment>
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`] = `
</span>
}
/>
+ <AlmSettingsInstanceDropdown
+ almInstances={
+ Array [
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ ]
+ }
+ onChangeConfig={[MockFunction]}
+ />
<DeferredSpinner
loading={false}
>
@@ -54,24 +69,21 @@ exports[`should render correctly: error 1`] = `
</span>
}
/>
- <div
- className="display-flex-justify-center"
- >
- <div
- className="boxed-group padded width-50 huge-spacer-top"
- >
- <h2
- className="big-spacer-bottom"
- >
- onboarding.create_project.github.warning.title
- </h2>
- <Alert
- variant="warning"
- >
- onboarding.create_project.github.warning.message
- </Alert>
- </div>
- </div>
+ <AlmSettingsInstanceDropdown
+ almInstances={
+ Array [
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ ]
+ }
+ onChangeConfig={[MockFunction]}
+ />
</div>
`;
@@ -92,36 +104,21 @@ exports[`should render correctly: error for admin 1`] = `
</span>
}
/>
- <div
- className="display-flex-justify-center"
- >
- <div
- className="boxed-group padded width-50 huge-spacer-top"
- >
- <h2
- className="big-spacer-bottom"
- >
- onboarding.create_project.github.warning.title
- </h2>
- <Alert
- variant="warning"
- >
- <FormattedMessage
- defaultMessage="onboarding.create_project.github.warning.message_admin"
- id="onboarding.create_project.github.warning.message_admin"
- values={
- Object {
- "link": <ForwardRef(Link)
- to="/admin/settings?category=almintegration"
- >
- onboarding.create_project.github.warning.message_admin.link
- </ForwardRef(Link)>,
- }
- }
- />
- </Alert>
- </div>
- </div>
+ <AlmSettingsInstanceDropdown
+ almInstances={
+ Array [
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ ]
+ }
+ onChangeConfig={[MockFunction]}
+ />
</div>
`;
@@ -161,6 +158,21 @@ exports[`should render correctly: no repositories 1`] = `
</span>
}
/>
+ <AlmSettingsInstanceDropdown
+ almInstances={
+ Array [
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ ]
+ }
+ onChangeConfig={[MockFunction]}
+ />
<DeferredSpinner
loading={false}
>
@@ -214,6 +226,21 @@ exports[`should render correctly: organizations 1`] = `
</span>
}
/>
+ <AlmSettingsInstanceDropdown
+ almInstances={
+ Array [
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ ]
+ }
+ onChangeConfig={[MockFunction]}
+ />
<DeferredSpinner
loading={false}
>
@@ -279,6 +306,21 @@ exports[`should render correctly: repositories 1`] = `
</span>
}
/>
+ <AlmSettingsInstanceDropdown
+ almInstances={
+ Array [
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ Object {
+ "alm": "github",
+ "key": "key",
+ },
+ ]
+ }
+ onChangeConfig={[MockFunction]}
+ />
<DeferredSpinner
loading={false}
>
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<ProjectCreationMenu['props']> = {}) {
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)}
/>