Browse Source

SONAR-17587 Allow project onboarding when multiple Github integrations are configured

tags/9.8.0.63668
Revanshu Paliwal 1 year ago
parent
commit
7fe3240e62
23 changed files with 268 additions and 144 deletions
  1. 28
    0
      server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts
  2. 2
    0
      server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts
  3. 2
    2
      server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx
  4. 2
    2
      server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx
  5. 3
    3
      server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
  6. 59
    31
      server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx
  7. 17
    2
      server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx
  8. 2
    2
      server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx
  9. 2
    2
      server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx
  10. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx
  11. 36
    3
      server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProject-it.tsx
  12. 4
    4
      server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx
  13. 3
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreateRenderer-test.tsx
  14. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx
  15. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap
  16. 6
    36
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap
  17. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
  18. 90
    48
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap
  19. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap
  20. 1
    1
      server/sonar-web/src/main/js/apps/create/project/constants.ts
  21. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx
  22. 2
    0
      server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx
  23. 3
    2
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 28
- 0
server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts View File

@@ -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);

+ 2
- 0
server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts View File

@@ -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() {

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx View File

@@ -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}
/>
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx View File

@@ -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" />}

+ 3
- 3
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx View File

@@ -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}
/>
);
}

+ 59
- 31
server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx View File

@@ -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}
/>
);
}

+ 17
- 2
server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx View File

@@ -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>

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx View File

@@ -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}
/>
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx View File

@@ -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" />}

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx View File

@@ -83,7 +83,7 @@ function shallowRender(overrides: Partial<AzureProjectCreateRendererProps> = {})
]}
showPersonalAccessTokenForm={false}
submittingToken={false}
onChangeConfig={jest.fn()}
onSelectedAlmInstanceChange={jest.fn()}
{...overrides}
/>
);

+ 36
- 3
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProject-it.tsx View File

@@ -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} />);
}

+ 4
- 4
server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx View File

@@ -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}
/>
);

+ 3
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreateRenderer-test.tsx View File

@@ -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=""

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx View File

@@ -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}

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap View File

@@ -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 {

+ 6
- 36
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap View File

@@ -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

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap View File

@@ -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>

+ 90
- 48
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap View File

@@ -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}
>

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap View File

@@ -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,

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/constants.ts View File

@@ -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];

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx View File

@@ -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']> = {}) {

+ 2
- 0
server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx View File

@@ -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)}
/>

+ 3
- 2
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -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.

Loading…
Cancel
Save