Browse Source

SONAR-13475 - List Github Enterprise repositories API (#2883)

fixup! SONAR-13475 - List Github Enterprise repositories API (#2883)
tags/8.4.0.35506
Duarte Meneses 4 years ago
parent
commit
db5fc17519
18 changed files with 502 additions and 174 deletions
  1. 4
    3
      server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDao.java
  2. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingMapper.java
  3. 12
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/alm/setting/ProjectAlmSettingMapper.xml
  4. 28
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/alm/setting/ProjectAlmSettingDaoTest.java
  5. 30
    11
      server/sonar-web/src/main/js/api/alm-integrations.ts
  6. 2
    11
      server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx
  7. 15
    10
      server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
  8. 103
    24
      server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx
  9. 105
    65
      server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx
  10. 2
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreate-test.tsx
  11. 14
    24
      server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx
  12. 74
    7
      server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx
  13. 8
    2
      server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreateRenderer-test.tsx
  14. 1
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreate-test.tsx.snap
  15. 31
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
  16. 49
    7
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap
  17. 8
    8
      sonar-core/src/main/resources/org/sonar/l10n/core.properties
  18. 14
    0
      sonar-ws/src/main/protobuf/ws-alm_integrations.proto

+ 4
- 3
server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDao.java View File

import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactory; import org.sonar.core.util.UuidFactory;
import org.sonar.db.Dao; import org.sonar.db.Dao;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.alm.AlmAppInstallDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.project.ProjectDto; import org.sonar.db.project.ProjectDto;


import static org.sonar.db.DatabaseUtils.executeLargeInputs; import static org.sonar.db.DatabaseUtils.executeLargeInputs;
public List<ProjectAlmSettingDto> selectByAlmSettingAndSlugs(DbSession dbSession, AlmSettingDto almSettingDto, Set<String> almSlugs) { public List<ProjectAlmSettingDto> selectByAlmSettingAndSlugs(DbSession dbSession, AlmSettingDto almSettingDto, Set<String> almSlugs) {
return executeLargeInputs(almSlugs, slugs -> getMapper(dbSession).selectByAlmSettingAndSlugs(almSettingDto.getUuid(), slugs)); return executeLargeInputs(almSlugs, slugs -> getMapper(dbSession).selectByAlmSettingAndSlugs(almSettingDto.getUuid(), slugs));
} }

public List<ProjectAlmSettingDto> selectByAlmSettingAndRepos(DbSession dbSession, AlmSettingDto almSettingDto, Set<String> almRepos) {
return executeLargeInputs(almRepos, repos -> getMapper(dbSession).selectByAlmSettingAndRepos(almSettingDto.getUuid(), repos));
}
} }

+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingMapper.java View File

void deleteByAlmSettingUuid(@Param("almSettingUuid") String almSettingUuid); void deleteByAlmSettingUuid(@Param("almSettingUuid") String almSettingUuid);


List<ProjectAlmSettingDto> selectByAlmSettingAndSlugs(@Param("almSettingUuid") String almSettingUuid, @Param("slugs") List<String> slugs); List<ProjectAlmSettingDto> selectByAlmSettingAndSlugs(@Param("almSettingUuid") String almSettingUuid, @Param("slugs") List<String> slugs);

List<ProjectAlmSettingDto> selectByAlmSettingAndRepos(@Param("almSettingUuid") String almSettingUuid, @Param("repos") List<String> repos);
} }

+ 12
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/alm/setting/ProjectAlmSettingMapper.xml View File

</foreach> </foreach>
</select> </select>


<select id="selectByAlmSettingAndRepos" parameterType="string" resultType="org.sonar.db.alm.setting.ProjectAlmSettingDto">
select <include refid="sqlColumns"/>
from
project_alm_settings p
where
alm_setting_uuid=#{almSettingUuid, jdbcType=VARCHAR}
and alm_repo in
<foreach collection="repos" open="(" close=")" item="repo" separator=",">
#{repo, jdbcType=VARCHAR}
</foreach>
</select>

<insert id="insert" parameterType="Map" useGeneratedKeys="false"> <insert id="insert" parameterType="Map" useGeneratedKeys="false">
INSERT INTO project_alm_settings INSERT INTO project_alm_settings
( (

+ 28
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/alm/setting/ProjectAlmSettingDaoTest.java View File

assertThat(underTest.selectByAlmSettingAndSlugs(dbSession, almSettingsDto, new HashSet<>())).isEmpty(); assertThat(underTest.selectByAlmSettingAndSlugs(dbSession, almSettingsDto, new HashSet<>())).isEmpty();
} }


@Test
public void select_by_alm_setting_and_repos() {
when(uuidFactory.create()).thenReturn(A_UUID);
AlmSettingDto almSettingsDto = db.almSettings().insertGitHubAlmSetting();
ProjectDto project = db.components().insertPrivateProjectDto();
ProjectAlmSettingDto githubProjectAlmSettingDto = newGithubProjectAlmSettingDto(almSettingsDto, project);
githubProjectAlmSettingDto.setAlmRepo("repo1");
underTest.insertOrUpdate(dbSession, githubProjectAlmSettingDto);
ProjectAlmSettingDto githubProjectAlmSettingDto2 = newGithubProjectAlmSettingDto(almSettingsDto, db.components().insertPrivateProjectDto());
githubProjectAlmSettingDto2.setAlmRepo("repo2");
when(uuidFactory.create()).thenReturn(A_UUID + 1);
underTest.insertOrUpdate(dbSession, githubProjectAlmSettingDto2);

Set<String> repos = new HashSet<>();
repos.add("repo1");
assertThat(underTest.selectByAlmSettingAndRepos(dbSession, almSettingsDto, repos))
.extracting(ProjectAlmSettingDto::getProjectUuid, ProjectAlmSettingDto::getSummaryCommentEnabled)
.containsExactly(tuple(project.getUuid(), githubProjectAlmSettingDto.getSummaryCommentEnabled()));
}

@Test
public void select_with_no_repos_return_empty() {
when(uuidFactory.create()).thenReturn(A_UUID);
AlmSettingDto almSettingsDto = db.almSettings().insertGitHubAlmSetting();

assertThat(underTest.selectByAlmSettingAndRepos(dbSession, almSettingsDto, new HashSet<>())).isEmpty();
}

@Test @Test
public void update_existing_binding() { public void update_existing_binding() {
when(uuidFactory.create()).thenReturn(A_UUID); when(uuidFactory.create()).thenReturn(A_UUID);

+ 30
- 11
server/sonar-web/src/main/js/api/alm-integrations.ts View File

}); });
} }


export function getGithubClientId(almSetting: string): Promise<{ clientId: string }> {
export function getGithubClientId(almSetting: string): Promise<{ clientId?: string }> {
return getJSON('/api/alm_integrations/get_github_client_id', { almSetting }); return getJSON('/api/alm_integrations/get_github_client_id', { almSetting });
} }


export function importGithubRepository(
almSetting: string,
organization: string,
repositoryKey: string
): Promise<{ project: ProjectBase }> {
return postJSON('/api/alm_integrations/import_github_project', {
almSetting,
organization,
repositoryKey
}).catch(throwGlobalError);
}

export function getGithubOrganizations( export function getGithubOrganizations(
almSetting: string, almSetting: string,
token: string token: string
): Promise<{ organizations: GithubOrganization[] }> { ): Promise<{ organizations: GithubOrganization[] }> {
return getJSON('/api/alm_integrations/list_github_enterprise_organizations', {
return getJSON('/api/alm_integrations/list_github_organizations', {
almSetting, almSetting,
token token
}).catch((response?: Response) => {
if (response && response.status !== 400) {
throwGlobalError(response);
}
}); });
} }


export function getGithubRepositories(
almSetting: string,
organization: string,
p = 1,
query?: string
): Promise<{ repositories: GithubRepository[]; paging: T.Paging }> {
return getJSON('/api/alm_integrations/list_github_enterprise_repositories', {
export function getGithubRepositories(data: {
almSetting: string;
organization: string;
ps: number;
p?: number;
query?: string;
}): Promise<{ repositories: GithubRepository[]; paging: T.Paging }> {
const { almSetting, organization, ps, p = 1, query } = data;
return getJSON('/api/alm_integrations/list_github_repositories', {
almSetting, almSetting,
organization, organization,
p, p,
query: query || undefined
});
ps,
q: query || undefined
}).catch(throwGlobalError);
} }

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

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; import * as React from 'react';
import { connect } from 'react-redux';
import { WithRouterProps } from 'react-router'; import { WithRouterProps } from 'react-router';
import { import {
checkPersonalAccessTokenIsValid, checkPersonalAccessTokenIsValid,
searchForBitbucketServerRepositories, searchForBitbucketServerRepositories,
setAlmPersonalAccessToken setAlmPersonalAccessToken
} from '../../../api/alm-integrations'; } from '../../../api/alm-integrations';
import { getAppState, Store } from '../../../store/rootReducer';
import { import {
BitbucketProject, BitbucketProject,
BitbucketProjectRepositories, BitbucketProjectRepositories,
import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer'; import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer';


interface Props extends Pick<WithRouterProps, 'location'> { interface Props extends Pick<WithRouterProps, 'location'> {
canAdmin: boolean;
bitbucketSettings: AlmSettingsInstance[]; bitbucketSettings: AlmSettingsInstance[];
canAdmin?: boolean;
loadingBindings: boolean; loadingBindings: boolean;
onProjectCreate: (projectKeys: string[]) => void; onProjectCreate: (projectKeys: string[]) => void;
} }
tokenValidationFailed: boolean; tokenValidationFailed: boolean;
} }


export class BitbucketProjectCreate extends React.PureComponent<Props, State> {
export default class BitbucketProjectCreate extends React.PureComponent<Props, State> {
mounted = false; mounted = false;


constructor(props: Props) { constructor(props: Props) {
); );
} }
} }

const mapStateToProps = (state: Store) => {
const { canAdmin } = getAppState(state);
return { canAdmin };
};

export default connect(mapStateToProps)(BitbucketProjectCreate);

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



export class CreateProjectPage extends React.PureComponent<Props, State> { export class CreateProjectPage extends React.PureComponent<Props, State> {
mounted = false; mounted = false;
state: State = { bitbucketSettings: [], githubSettings: [], loading: false };
state: State = { bitbucketSettings: [], githubSettings: [], loading: true };


componentDidMount() { componentDidMount() {
const { const {
}); });
}; };


handleProjectCreate = (projectKeys: string[]) => {
if (projectKeys.length === 1) {
this.props.router.push(getProjectUrl(projectKeys[0]));
}
};

handleModeSelect = (mode: CreateProjectModes) => { handleModeSelect = (mode: CreateProjectModes) => {
const { router, location } = this.props; const { router, location } = this.props;
router.push({ router.push({
}); });
}; };


handleProjectCreate = (projectKeys: string[]) => {
if (projectKeys.length === 1) {
this.props.router.push(getProjectUrl(projectKeys[0]));
}
};

renderForm(mode?: CreateProjectModes) { renderForm(mode?: CreateProjectModes) {
const { const {
appState: { canAdmin }, appState: { canAdmin },
location
location,
router
} = this.props; } = this.props;
const { bitbucketSettings, githubSettings, loading } = this.state; const { bitbucketSettings, githubSettings, loading } = this.state;


case CreateProjectModes.BitbucketServer: { case CreateProjectModes.BitbucketServer: {
return ( return (
<BitbucketProjectCreate <BitbucketProjectCreate
canAdmin={!!canAdmin}
bitbucketSettings={bitbucketSettings} bitbucketSettings={bitbucketSettings}
loadingBindings={loading} loadingBindings={loading}
location={location} location={location}
return ( return (
<GitHubProjectCreate <GitHubProjectCreate
canAdmin={!!canAdmin} canAdmin={!!canAdmin}
code={location.query?.code}
settings={githubSettings[0]}
loadingBindings={loading}
location={location}
onProjectCreate={this.handleProjectCreate}
router={router}
settings={githubSettings}
/> />
); );
} }

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

*/ */
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { WithRouterProps } from 'react-router';
import { getHostUrl } from 'sonar-ui-common/helpers/urls'; import { getHostUrl } from 'sonar-ui-common/helpers/urls';
import { import {
getGithubClientId, getGithubClientId,
getGithubOrganizations, getGithubOrganizations,
getGithubRepositories
getGithubRepositories,
importGithubRepository
} from '../../../api/alm-integrations'; } from '../../../api/alm-integrations';
import { GithubOrganization, GithubRepository } from '../../../types/alm-integration'; import { GithubOrganization, GithubRepository } from '../../../types/alm-integration';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
import GitHubProjectCreateRenderer from './GitHubProjectCreateRenderer'; import GitHubProjectCreateRenderer from './GitHubProjectCreateRenderer';


interface Props {
interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
canAdmin: boolean; canAdmin: boolean;
code?: string;
settings?: AlmSettingsInstance;
loadingBindings: boolean;
onProjectCreate: (projectKeys: string[]) => void;
settings: AlmSettingsInstance[];
} }


interface State { interface State {
error: boolean; error: boolean;
loading: boolean;
importing: boolean;
loadingOrganizations: boolean;
loadingRepositories: boolean; loadingRepositories: boolean;
organizations: GithubOrganization[]; organizations: GithubOrganization[];
repositoryPaging: T.Paging; repositoryPaging: T.Paging;
searchQuery: string; searchQuery: string;
selectedOrganization?: GithubOrganization; selectedOrganization?: GithubOrganization;
selectedRepository?: GithubRepository; selectedRepository?: GithubRepository;
settings?: AlmSettingsInstance;
} }


const REPOSITORY_PAGE_SIZE = 30; const REPOSITORY_PAGE_SIZE = 30;


this.state = { this.state = {
error: false, error: false,
loading: true,
importing: false,
loadingOrganizations: true,
loadingRepositories: false, loadingRepositories: false,
organizations: [], organizations: [],
repositories: [], repositories: [],
repositoryPaging: { pageSize: REPOSITORY_PAGE_SIZE, total: 0, pageIndex: 1 }, repositoryPaging: { pageSize: REPOSITORY_PAGE_SIZE, total: 0, pageIndex: 1 },
searchQuery: ''
searchQuery: '',
settings: props.settings[0]
}; };


this.triggerSearch = debounce(this.triggerSearch, 250); this.triggerSearch = debounce(this.triggerSearch, 250);
} }


componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps: Props) {
if (!prevProps.settings && this.props.settings) {
this.initialize();
if (prevProps.settings.length === 0 && this.props.settings.length > 0) {
this.setState({ settings: this.props.settings[0] }, () => this.initialize());
} }
} }


} }


async initialize() { async initialize() {
const { code, settings } = this.props;
const { location, router } = this.props;
const { settings } = this.state;


if (!settings) {
if (!settings || !settings.url) {
this.setState({ error: true }); this.setState({ error: true });
return; return;
} else { } else {
this.setState({ error: false }); this.setState({ error: false });
} }


const code = location.query?.code;

try { try {
if (!code) { if (!code) {
await this.redirectToGithub(settings); await this.redirectToGithub(settings);
} else { } else {
delete location.query.code;
router.replace(location);
await this.fetchOrganizations(settings, code); await this.fetchOrganizations(settings, code);
} }
} catch (e) { } catch (e) {
} }


async redirectToGithub(settings: AlmSettingsInstance) { async redirectToGithub(settings: AlmSettingsInstance) {
if (!settings.url) {
return;
}

const { clientId } = await getGithubClientId(settings.key); const { clientId } = await getGithubClientId(settings.key);


if (!clientId) {
this.setState({ error: true });
return;
}

const queryParams = [ const queryParams = [
{ param: 'client_id', value: clientId }, { param: 'client_id', value: clientId },
{ param: 'redirect_uri', value: `${getHostUrl()}/projects/create?mode=${AlmKeys.GitHub}` } { param: 'redirect_uri', value: `${getHostUrl()}/projects/create?mode=${AlmKeys.GitHub}` }
.map(({ param, value }) => `${param}=${value}`) .map(({ param, value }) => `${param}=${value}`)
.join('&'); .join('&');


window.location.replace(`https://github.com/login/oauth/authorize?${queryParams}`);
let instanceRootUrl;
// Strip the api section from the url, since we're not hitting the api here.
if (settings.url.includes('/api/v3')) {
// GitHub Enterprise
instanceRootUrl = settings.url.replace('/api/v3', '');
} else {
// github.com
instanceRootUrl = settings.url.replace('api.', '');
}

// strip the trailing /
instanceRootUrl = instanceRootUrl.replace(/\/$/, '');
window.location.replace(`${instanceRootUrl}/login/oauth/authorize?${queryParams}`);
} }


async fetchOrganizations(settings: AlmSettingsInstance, token: string) { async fetchOrganizations(settings: AlmSettingsInstance, token: string) {
const { organizations } = await getGithubOrganizations(settings.key, token); const { organizations } = await getGithubOrganizations(settings.key, token);


if (this.mounted) { if (this.mounted) {
this.setState({ loading: false, organizations });
this.setState({ loadingOrganizations: false, organizations });
} }
} }


async fetchRepositories(params: { organizationKey: string; page?: number; query?: string }) { async fetchRepositories(params: { organizationKey: string; page?: number; query?: string }) {
const { organizationKey, page = 1, query } = params; const { organizationKey, page = 1, query } = params;
const { settings } = this.props;
const { settings } = this.state;


if (!settings) { if (!settings) {
this.setState({ error: true }); this.setState({ error: true });


this.setState({ loadingRepositories: true }); this.setState({ loadingRepositories: true });


const data = await getGithubRepositories(settings.key, organizationKey, page, query);
try {
const data = await getGithubRepositories({
almSetting: settings.key,
organization: organizationKey,
ps: REPOSITORY_PAGE_SIZE,
p: page,
query
});


if (this.mounted) {
this.setState(({ repositories }) => ({
loadingRepositories: false,
repositoryPaging: data.paging,
repositories: page === 1 ? data.repositories : [...repositories, ...data.repositories]
}));
if (this.mounted) {
this.setState(({ repositories }) => ({
loadingRepositories: false,
repositoryPaging: data.paging,
repositories: page === 1 ? data.repositories : [...repositories, ...data.repositories]
}));
}
} catch (_) {
if (this.mounted) {
this.setState({
loadingRepositories: false,
repositoryPaging: { pageIndex: 1, pageSize: REPOSITORY_PAGE_SIZE, total: 0 },
repositories: []
});
}
} }
} }


triggerSearch = (query: string) => { triggerSearch = (query: string) => {
const { selectedOrganization } = this.state; const { selectedOrganization } = this.state;
if (selectedOrganization) { if (selectedOrganization) {
this.setState({ selectedRepository: undefined });
this.fetchRepositories({ organizationKey: selectedOrganization.key, query }); this.fetchRepositories({ organizationKey: selectedOrganization.key, query });
} }
}; };


handleSelectOrganization = (key: string) => { handleSelectOrganization = (key: string) => {
this.setState(({ organizations }) => ({ this.setState(({ organizations }) => ({
searchQuery: '',
selectedRepository: undefined,
selectedOrganization: organizations.find(o => o.key === key) selectedOrganization: organizations.find(o => o.key === key)
})); }));
this.fetchRepositories({ organizationKey: key }); this.fetchRepositories({ organizationKey: key });
} }
}; };


handleImportRepository = async () => {
const { selectedOrganization, selectedRepository, settings } = this.state;

if (settings && selectedOrganization && selectedRepository) {
this.setState({ importing: true });

try {
const { project } = await importGithubRepository(
settings.key,
selectedOrganization.key,
selectedRepository.key
);

this.props.onProjectCreate([project.key]);
} finally {
if (this.mounted) {
this.setState({ importing: false });
}
}
}
};

render() { render() {
const { canAdmin } = this.props;
const { canAdmin, loadingBindings } = this.props;
const { const {
error, error,
loading,
importing,
loadingOrganizations,
loadingRepositories, loadingRepositories,
organizations, organizations,
repositoryPaging, repositoryPaging,
selectedOrganization, selectedOrganization,
selectedRepository selectedRepository
} = this.state; } = this.state;

return ( return (
<GitHubProjectCreateRenderer <GitHubProjectCreateRenderer
canAdmin={canAdmin} canAdmin={canAdmin}
error={error} error={error}
loading={loading}
importing={importing}
loadingBindings={loadingBindings}
loadingOrganizations={loadingOrganizations}
loadingRepositories={loadingRepositories} loadingRepositories={loadingRepositories}
onImportRepository={this.handleImportRepository}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
onSearch={this.handleSearch} onSearch={this.handleSearch}
onSelectOrganization={this.handleSelectOrganization} onSelectOrganization={this.handleSelectOrganization}

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

import * as React from 'react'; import * as React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { Button } from 'sonar-ui-common/components/controls/buttons';
import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; import ListFooter from 'sonar-ui-common/components/controls/ListFooter';
import Radio from 'sonar-ui-common/components/controls/Radio'; import Radio from 'sonar-ui-common/components/controls/Radio';
import SearchBox from 'sonar-ui-common/components/controls/SearchBox'; import SearchBox from 'sonar-ui-common/components/controls/SearchBox';
export interface GitHubProjectCreateRendererProps { export interface GitHubProjectCreateRendererProps {
canAdmin: boolean; canAdmin: boolean;
error: boolean; error: boolean;
loading: boolean;
importing: boolean;
loadingBindings: boolean;
loadingOrganizations: boolean;
loadingRepositories: boolean; loadingRepositories: boolean;
onImportRepository: () => void;
onLoadMore: () => void; onLoadMore: () => void;
onSearch: (q: string) => void; onSearch: (q: string) => void;
onSelectOrganization: (key: string) => void; onSelectOrganization: (key: string) => void;
return { value: key, label: name }; return { value: key, label: name };
} }


export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRendererProps) {
const handleSearch = (organizations: GithubOrganization[]) => (q: string) =>
Promise.resolve(organizations.filter(o => !q || o.name.includes(q)).map(orgToOption));

function renderRepositoryList(props: GitHubProjectCreateRendererProps) {
const { const {
canAdmin,
error,
loading,
importing,
loadingRepositories, loadingRepositories,
organizations,
repositories, repositories,
repositoryPaging, repositoryPaging,
searchQuery, searchQuery,
selectedRepository selectedRepository
} = props; } = props;


const isChecked = (repository: GithubRepository) =>
!!repository.sqProjectKey ||
(!!selectedRepository && selectedRepository.key === repository.key);

const isDisabled = (repository: GithubRepository) =>
!!repository.sqProjectKey || loadingRepositories || importing;

return (
selectedOrganization &&
repositories && (
<div className="boxed-group padded display-flex-wrap">
<div className="width-100">
<SearchBox
className="big-spacer-bottom"
onChange={props.onSearch}
placeholder={translate('onboarding.create_project.search_repositories')}
value={searchQuery}
/>
</div>

{repositories.length === 0 ? (
<div className="padded">
<DeferredSpinner loading={loadingRepositories}>
{translate('no_results')}
</DeferredSpinner>
</div>
) : (
repositories.map(r => (
<Radio
className="spacer-top spacer-bottom padded create-project-github-repository"
key={r.key}
checked={isChecked(r)}
disabled={isDisabled(r)}
value={r.key}
onCheck={props.onSelectRepository}>
<div className="big overflow-hidden" title={r.name}>
<div className="text-ellipsis">{r.name}</div>
{r.sqProjectKey && (
<em className="notice text-muted-2 small display-flex-center">
{translate('onboarding.create_project.repository_imported')}
<CheckIcon className="little-spacer-left" size={12} />
</em>
)}
</div>
</Radio>
))
)}

<div className="display-flex-justify-center width-100">
<ListFooter
count={repositories.length}
total={repositoryPaging.total}
loadMore={props.onLoadMore}
loading={loadingRepositories}
/>
</div>
</div>
)
);
}

export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRendererProps) {
const {
canAdmin,
error,
importing,
loadingBindings,
loadingOrganizations,
organizations,
selectedOrganization,
selectedRepository
} = props;

if (loadingBindings) {
return <DeferredSpinner />;
}

return ( return (
<div> <div>
<CreateProjectPageHeader <CreateProjectPageHeader
additionalActions={
selectedOrganization && (
<div className="display-flex-center pull-right">
<DeferredSpinner className="spacer-right" loading={importing} />
<Button
className="button-large button-primary"
disabled={!selectedRepository || importing}
onClick={props.onImportRepository}>
{translate('onboarding.create_project.import_selected_repo')}
</Button>
</div>
)
}
title={ title={
<span className="text-middle display-flex-center"> <span className="text-middle display-flex-center">
<img <img
</div> </div>
</div> </div>
) : ( ) : (
<DeferredSpinner loading={loading}>
<DeferredSpinner loading={loadingOrganizations}>
<div className="form-field"> <div className="form-field">
<label>{translate('onboarding.create_project.github.choose_organization')}</label> <label>{translate('onboarding.create_project.github.choose_organization')}</label>
{organizations.length > 0 ? ( {organizations.length > 0 ? (
<SearchSelect <SearchSelect
defaultOptions={organizations.slice(0, 10).map(orgToOption)}
onSearch={(q: string) =>
Promise.resolve(
organizations.filter(o => !q || o.name.includes(q)).map(orgToOption)
)
}
defaultOptions={organizations.map(orgToOption)}
onSearch={handleSearch(organizations)}
minimumQueryLength={0} minimumQueryLength={0}
onSelect={({ value }) => props.onSelectOrganization(value)} onSelect={({ value }) => props.onSelectOrganization(value)}
value={selectedOrganization && orgToOption(selectedOrganization)} value={selectedOrganization && orgToOption(selectedOrganization)}
/> />
) : ( ) : (
!loading && (
!loadingOrganizations && (
<Alert className="spacer-top" variant="error"> <Alert className="spacer-top" variant="error">
{canAdmin ? ( {canAdmin ? (
<FormattedMessage <FormattedMessage
</DeferredSpinner> </DeferredSpinner>
)} )}


{selectedOrganization && repositories && (
<div className="boxed-group padded display-flex-wrap">
<div className="width-100">
<SearchBox
className="big-spacer-bottom"
onChange={props.onSearch}
placeholder={translate('onboarding.create_project.search_repositories')}
value={searchQuery}
/>
</div>

{repositories.length === 0 ? (
<div className="padded">
<DeferredSpinner loading={loadingRepositories}>
{translate('no_results')}
</DeferredSpinner>
</div>
) : (
repositories.map(r => (
<Radio
className="spacer-top spacer-bottom padded create-project-github-repository"
key={r.key}
checked={
!!r.sqProjectKey || (!!selectedRepository && selectedRepository.key === r.key)
}
disabled={!!r.sqProjectKey || loadingRepositories || importing}
value={r.key}
onCheck={props.onSelectRepository}>
<div className="big overflow-hidden" title={r.name}>
<div className="overflow-hidden text-ellipsis">{r.name}</div>
{r.sqProjectKey && (
<em className="notice text-muted-2 small display-flex-center">
{translate('onboarding.create_project.repository_imported')}
<CheckIcon className="little-spacer-left" size={12} />
</em>
)}
</div>
</Radio>
))
)}

<div className="display-flex-justify-center width-100">
<ListFooter
count={repositories.length}
total={repositoryPaging.total}
loadMore={props.onLoadMore}
loading={loadingRepositories}
/>
</div>
</div>
)}
{renderRepositoryList(props)}
</div> </div>
); );
} }

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

import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { mockLocation } from '../../../../helpers/testMocks'; import { mockLocation } from '../../../../helpers/testMocks';
import { AlmKeys } from '../../../../types/alm-settings'; import { AlmKeys } from '../../../../types/alm-settings';
import { BitbucketProjectCreate } from '../BitbucketProjectCreate';
import BitbucketProjectCreate from '../BitbucketProjectCreate';


jest.mock('../../../../api/alm-integrations', () => { jest.mock('../../../../api/alm-integrations', () => {
const { mockBitbucketProject, mockBitbucketRepository } = jest.requireActual( const { mockBitbucketProject, mockBitbucketRepository } = jest.requireActual(
function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) { function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) {
return shallow<BitbucketProjectCreate>( return shallow<BitbucketProjectCreate>(
<BitbucketProjectCreate <BitbucketProjectCreate
canAdmin={false}
bitbucketSettings={[mockAlmSettingsInstance({ alm: AlmKeys.Bitbucket, key: 'foo' })]} bitbucketSettings={[mockAlmSettingsInstance({ alm: AlmKeys.Bitbucket, key: 'foo' })]}
loadingBindings={false} loadingBindings={false}
location={mockLocation()} location={mockLocation()}

+ 14
- 24
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx View File

}); });


it('should render correctly if the manual method is selected', () => { it('should render correctly if the manual method is selected', () => {
const push = jest.fn();
const location = { query: { mode: CreateProjectModes.Manual } };
const wrapper = shallowRender({ router: mockRouter({ push }) });

wrapper.instance().handleModeSelect(CreateProjectModes.Manual);
expect(push).toBeCalledWith(expect.objectContaining(location));

expect(wrapper.setProps({ location: mockLocation(location) })).toMatchSnapshot();
expect(
shallowRender({
location: mockLocation({ query: { mode: CreateProjectModes.Manual } })
})
).toMatchSnapshot();
}); });


it('should render correctly if the BBS method is selected', () => { it('should render correctly if the BBS method is selected', () => {
const push = jest.fn();
const location = { query: { mode: CreateProjectModes.BitbucketServer } };
const wrapper = shallowRender({ router: mockRouter({ push }) });

wrapper.instance().handleModeSelect(CreateProjectModes.BitbucketServer);
expect(push).toBeCalledWith(expect.objectContaining(location));

expect(wrapper.setProps({ location: mockLocation(location) })).toMatchSnapshot();
expect(
shallowRender({
location: mockLocation({ query: { mode: CreateProjectModes.BitbucketServer } })
})
).toMatchSnapshot();
}); });


it('should render correctly if the GitHub method is selected', () => { it('should render correctly if the GitHub method is selected', () => {
const push = jest.fn();
const location = { query: { mode: CreateProjectModes.GitHub } };
const wrapper = shallowRender({ router: mockRouter({ push }) });

wrapper.instance().handleModeSelect(CreateProjectModes.GitHub);
expect(push).toBeCalledWith(expect.objectContaining(location));

expect(wrapper.setProps({ location: mockLocation(location) })).toMatchSnapshot();
const wrapper = shallowRender({
location: mockLocation({ query: { mode: CreateProjectModes.GitHub } })
});
expect(wrapper).toMatchSnapshot();
}); });


function shallowRender(props: Partial<CreateProjectPage['props']> = {}) { function shallowRender(props: Partial<CreateProjectPage['props']> = {}) {

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

import { import {
getGithubClientId, getGithubClientId,
getGithubOrganizations, getGithubOrganizations,
getGithubRepositories
getGithubRepositories,
importGithubRepository
} from '../../../../api/alm-integrations'; } from '../../../../api/alm-integrations';
import { mockGitHubRepository } from '../../../../helpers/mocks/alm-integrations'; import { mockGitHubRepository } from '../../../../helpers/mocks/alm-integrations';
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
import GitHubProjectCreate from '../GitHubProjectCreate'; import GitHubProjectCreate from '../GitHubProjectCreate';


jest.mock('../../../../api/alm-integrations', () => ({ jest.mock('../../../../api/alm-integrations', () => ({
getGithubClientId: jest.fn().mockResolvedValue({ clientId: 'client-id-124' }), getGithubClientId: jest.fn().mockResolvedValue({ clientId: 'client-id-124' }),
getGithubOrganizations: jest.fn().mockResolvedValue({ organizations: [] }), getGithubOrganizations: jest.fn().mockResolvedValue({ organizations: [] }),
getGithubRepositories: jest.fn().mockResolvedValue({ repositories: [], paging: {} })
getGithubRepositories: jest.fn().mockResolvedValue({ repositories: [], paging: {} }),
importGithubRepository: jest.fn().mockResolvedValue({ project: {} })
})); }));


const originalLocation = window.location; const originalLocation = window.location;
}); });


it('should handle no settings', async () => { it('should handle no settings', async () => {
const wrapper = shallowRender({ settings: undefined });
const wrapper = shallowRender({ settings: [] });
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);
expect(wrapper.state().error).toBe(true); expect(wrapper.state().error).toBe(true);
}); });
expect(window.location.replace).toBeCalled(); expect(window.location.replace).toBeCalled();
}); });


it('should redirect when no code - github.com', async () => {
const wrapper = shallowRender({
settings: [mockAlmSettingsInstance({ key: 'a', url: 'api.github.com' })]
});
await waitAndUpdate(wrapper);

expect(getGithubClientId).toBeCalled();
expect(window.location.replace).toBeCalledWith(
'github.com/login/oauth/authorize?client_id=client-id-124&redirect_uri=http://localhost/projects/create?mode=github'
);
});

it('should not redirect when invalid clientId', async () => {
(getGithubClientId as jest.Mock).mockResolvedValue({ clientId: undefined });
const wrapper = shallowRender();
await waitAndUpdate(wrapper);

expect(wrapper.state().error).toBe(true);
expect(window.location.replace).not.toBeCalled();
});

it('should fetch organizations when code', async () => { it('should fetch organizations when code', async () => {
const organizations = [ const organizations = [
{ key: '1', name: 'org1' }, { key: '1', name: 'org1' },
{ key: '2', name: 'org2' } { key: '2', name: 'org2' }
]; ];
(getGithubOrganizations as jest.Mock).mockResolvedValueOnce({ organizations }); (getGithubOrganizations as jest.Mock).mockResolvedValueOnce({ organizations });
const wrapper = shallowRender({ code: '123456' });
const replace = jest.fn();
const wrapper = shallowRender({
location: mockLocation({ query: { code: '123456' } }),
router: mockRouter({ replace })
});
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);


expect(replace).toBeCalled();
expect(getGithubOrganizations).toBeCalled(); expect(getGithubOrganizations).toBeCalled();
expect(wrapper.state().organizations).toBe(organizations); expect(wrapper.state().organizations).toBe(organizations);
}); });
repositories, repositories,
paging: { total: 1, pageIndex: 1 } paging: { total: 1, pageIndex: 1 }
}); });
const wrapper = shallowRender({ code: '123456' });
const wrapper = shallowRender({ location: mockLocation({ query: { code: '123456' } }) });
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);


wrapper.instance().handleSelectOrganization('1'); wrapper.instance().handleSelectOrganization('1');


await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);


expect(getGithubRepositories).toBeCalledWith('a', 'o1', 1, query);
expect(getGithubRepositories).toBeCalledWith({
almSetting: 'a',
organization: 'o1',
p: 1,
ps: 30,
query: 'query'
});
expect(wrapper.state().repositories).toEqual(repositories); expect(wrapper.state().repositories).toEqual(repositories);
}); });


expect(wrapper.state().selectedRepository).toBe(repo); expect(wrapper.state().selectedRepository).toBe(repo);
}); });


it('should handle importing', async () => {
const project = { key: 'new_project' };

(importGithubRepository as jest.Mock).mockResolvedValueOnce({ project });

const onProjectCreate = jest.fn();
const wrapper = shallowRender({ onProjectCreate });

wrapper.instance().handleImportRepository();
expect(importGithubRepository).not.toBeCalled();

const selectedOrganization = { key: 'org1', name: 'org1' };
const selectedRepository = mockGitHubRepository();
wrapper.setState({
selectedOrganization,
selectedRepository
});

wrapper.instance().handleImportRepository();
await waitAndUpdate(wrapper);
expect(importGithubRepository).toBeCalledWith(
'a',
selectedOrganization.key,
selectedRepository.key
);
expect(onProjectCreate).toBeCalledWith([project.key]);
});

function shallowRender(props: Partial<GitHubProjectCreate['props']> = {}) { function shallowRender(props: Partial<GitHubProjectCreate['props']> = {}) {
return shallow<GitHubProjectCreate>( return shallow<GitHubProjectCreate>(
<GitHubProjectCreate <GitHubProjectCreate
canAdmin={false} canAdmin={false}
settings={mockAlmSettingsInstance({ key: 'a' })}
loadingBindings={false}
location={mockLocation()}
onProjectCreate={jest.fn()}
router={mockRouter()}
settings={[mockAlmSettingsInstance({ key: 'a', url: 'geh.company.com/api/v3' })]}
{...props} {...props}
/> />
); );

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



it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default'); expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ loadingBindings: true })).toMatchSnapshot('loading');
expect(shallowRender({ error: true })).toMatchSnapshot('error'); expect(shallowRender({ error: true })).toMatchSnapshot('error');
expect(shallowRender({ canAdmin: true, error: true })).toMatchSnapshot('error for admin'); expect(shallowRender({ canAdmin: true, error: true })).toMatchSnapshot('error for admin');


}); });


describe('callback', () => { describe('callback', () => {
const onImportRepository = jest.fn();
const onSelectOrganization = jest.fn(); const onSelectOrganization = jest.fn();
const onSelectRepository = jest.fn(); const onSelectRepository = jest.fn();
const onSearch = jest.fn(); const onSearch = jest.fn();
const org = { key: 'o1', name: 'org' }; const org = { key: 'o1', name: 'org' };
const wrapper = shallowRender({ const wrapper = shallowRender({
onImportRepository,
onSelectOrganization, onSelectOrganization,
onSelectRepository, onSelectRepository,
onSearch, onSearch,


it('should be called when org is selected', () => { it('should be called when org is selected', () => {
const value = 'o1'; const value = 'o1';
wrapper.find(SearchSelect).props().onSelect!({ value });
wrapper.find(SearchSelect).simulate('select', { value });
expect(onSelectOrganization).toBeCalledWith(value); expect(onSelectOrganization).toBeCalledWith(value);
}); });


<GitHubProjectCreateRenderer <GitHubProjectCreateRenderer
canAdmin={false} canAdmin={false}
error={false} error={false}
loading={false}
importing={false}
loadingBindings={false}
loadingOrganizations={false}
loadingRepositories={false} loadingRepositories={false}
onImportRepository={jest.fn()}
onLoadMore={jest.fn()} onLoadMore={jest.fn()}
onSearch={jest.fn()} onSearch={jest.fn()}
onSelectOrganization={jest.fn()} onSelectOrganization={jest.fn()}

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

"key": "foo", "key": "foo",
} }
} }
canAdmin={false}
importing={false} importing={false}
loading={true} loading={true}
onImportRepository={[Function]} onImportRepository={[Function]}

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

className="page page-limited huge-spacer-bottom position-relative" className="page page-limited huge-spacer-bottom position-relative"
id="create-project" id="create-project"
> >
<Connect(BitbucketProjectCreate)
<BitbucketProjectCreate
bitbucketSettings={Array []} bitbucketSettings={Array []}
canAdmin={false}
loadingBindings={true} loadingBindings={true}
location={ location={
Object { Object {
> >
<GitHubProjectCreate <GitHubProjectCreate
canAdmin={false} canAdmin={false}
loadingBindings={true}
location={
Object {
"action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
"query": Object {
"mode": "github",
},
"search": "",
"state": Object {},
}
}
onProjectCreate={[Function]}
router={
Object {
"createHref": [MockFunction],
"createPath": [MockFunction],
"go": [MockFunction],
"goBack": [MockFunction],
"goForward": [MockFunction],
"isActive": [MockFunction],
"push": [MockFunction],
"replace": [MockFunction],
"setRouteLeaveHook": [MockFunction],
}
}
settings={Array []}
/> />
</div> </div>
</Fragment> </Fragment>

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

</div> </div>
`; `;


exports[`should render correctly: loading 1`] = `
<DeferredSpinner
timeout={100}
/>
`;

exports[`should render correctly: no repositories 1`] = ` exports[`should render correctly: no repositories 1`] = `
<div> <div>
<CreateProjectPageHeader <CreateProjectPageHeader
additionalActions={
<div
className="display-flex-center pull-right"
>
<DeferredSpinner
className="spacer-right"
loading={false}
timeout={100}
/>
<Button
className="button-large button-primary"
disabled={true}
onClick={[MockFunction]}
>
onboarding.create_project.import_selected_repo
</Button>
</div>
}
title={ title={
<span <span
className="text-middle display-flex-center" className="text-middle display-flex-center"
exports[`should render correctly: repositories 1`] = ` exports[`should render correctly: repositories 1`] = `
<div> <div>
<CreateProjectPageHeader <CreateProjectPageHeader
additionalActions={
<div
className="display-flex-center pull-right"
>
<DeferredSpinner
className="spacer-right"
loading={false}
timeout={100}
/>
<Button
className="button-large button-primary"
disabled={false}
onClick={[MockFunction]}
>
onboarding.create_project.import_selected_repo
</Button>
</div>
}
title={ title={
<span <span
className="text-middle display-flex-center" className="text-middle display-flex-center"
</div> </div>
<Radio <Radio
checked={false} checked={false}
className="spacer-top spacer-bottom padded github-repository"
className="spacer-top spacer-bottom padded create-project-github-repository"
disabled={false} disabled={false}
key="repo1" key="repo1"
onCheck={[MockFunction]} onCheck={[MockFunction]}
title="repository 1" title="repository 1"
> >
<div <div
className="overflow-hidden text-ellipsis"
className="text-ellipsis"
> >
repository 1 repository 1
</div> </div>
</Radio> </Radio>
<Radio <Radio
checked={true} checked={true}
className="spacer-top spacer-bottom padded github-repository"
className="spacer-top spacer-bottom padded create-project-github-repository"
disabled={true} disabled={true}
key="repo2" key="repo2"
onCheck={[MockFunction]} onCheck={[MockFunction]}
title="repository 1" title="repository 1"
> >
<div <div
className="overflow-hidden text-ellipsis"
className="text-ellipsis"
> >
repository 1 repository 1
</div> </div>
<em <em
className="notice text-muted-2 small display-flex-center" className="notice text-muted-2 small display-flex-center"
> >
onboarding.create_project.already_imported
onboarding.create_project.repository_imported
<CheckIcon <CheckIcon
className="little-spacer-left" className="little-spacer-left"
size={12} size={12}
</Radio> </Radio>
<Radio <Radio
checked={true} checked={true}
className="spacer-top spacer-bottom padded github-repository"
className="spacer-top spacer-bottom padded create-project-github-repository"
disabled={false} disabled={false}
key="repo3" key="repo3"
onCheck={[MockFunction]} onCheck={[MockFunction]}
title="repository 1" title="repository 1"
> >
<div <div
className="overflow-hidden text-ellipsis"
className="text-ellipsis"
> >
repository 1 repository 1
</div> </div>

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

settings.almintegration.feature.alm_repo_import.title=Import repositories from your ALM settings.almintegration.feature.alm_repo_import.title=Import repositories from your ALM
settings.almintegration.feature.alm_repo_import.description=Select repositories from your ALM, and import them into SonarQube. settings.almintegration.feature.alm_repo_import.description=Select repositories from your ALM, and import them into SonarQube.
settings.almintegration.feature.alm_repo_import.disabled_if_multiple_bbs_instances=Connecting to multiple Bitbucket Server instances will deactivate the {feature} feature. Projects will have to be set up manually. settings.almintegration.feature.alm_repo_import.disabled_if_multiple_bbs_instances=Connecting to multiple Bitbucket Server instances will deactivate the {feature} feature. Projects will have to be set up manually.
settings.almintegration.feature.alm_repo_import.disabled_if_multiple_github_instances=Connecting to multiple GitHub Enterprise instances will deactivate the {feature} feature. Projects will have to be set up manually.
settings.almintegration.feature.alm_repo_import.github.too_many_instances_x=You must have exactly 1 GitHub Enterprise instance configured in order to use this method. You currently have {0}.
settings.almintegration.feature.alm_repo_import.disabled_if_multiple_github_instances=Connecting to multiple GitHub instances will deactivate the {feature} feature. Projects will have to be set up manually.
settings.almintegration.feature.alm_repo_import.github.too_many_instances_x=You must have exactly 1 GitHub instance configured in order to use this method. You currently have {0}.
settings.almintegration.feature.alm_repo_import.github.requires_fields=Your configured instance must be provided with the App's {clientId} and {clientSecret}. settings.almintegration.feature.alm_repo_import.github.requires_fields=Your configured instance must be provided with the App's {clientId} and {clientSecret}.




onboarding.create_project.setup_manually=Create a project onboarding.create_project.setup_manually=Create a project
onboarding.create_project.select_method.manual=Manually onboarding.create_project.select_method.manual=Manually
onboarding.create_project.select_method.bitbucket=From Bitbucket Server onboarding.create_project.select_method.bitbucket=From Bitbucket Server
onboarding.create_project.select_method.github=From GitHub Enterprise
onboarding.create_project.select_method.github=From GitHub
onboarding.create_project.alm_not_configured=Currently not active onboarding.create_project.alm_not_configured=Currently not active
onboarding.create_project.check_alm_supported=Checking if available onboarding.create_project.check_alm_supported=Checking if available
onboarding.create_project.project_key=Project key onboarding.create_project.project_key=Project key
onboarding.create_project.too_many_alm_instances.github=You must have exactly 1 Bitbucket Server instance configured in order to use this method. onboarding.create_project.too_many_alm_instances.github=You must have exactly 1 Bitbucket Server instance configured in order to use this method.
onboarding.create_project.alm_instances_count_X=You currently have {0}. onboarding.create_project.alm_instances_count_X=You currently have {0}.
onboarding.create_project.zero_alm_instances.bitbucket=You must first configure a Bitbucket Server instance. onboarding.create_project.zero_alm_instances.bitbucket=You must first configure a Bitbucket Server instance.
onboarding.create_project.zero_alm_instances.github=You must first configure a GitHub Enterprise instance.
onboarding.create_project.zero_alm_instances.github=You must first configure a GitHub instance.
onboarding.create_project.no_bbs_binding=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method, but none were found. Either create the project manually, or contact your system administrator. onboarding.create_project.no_bbs_binding=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method, but none were found. Either create the project manually, or contact your system administrator.
onboarding.create_project.no_bbs_binding.admin=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method. You can configure instances under {url}. onboarding.create_project.no_bbs_binding.admin=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method. You can configure instances under {url}.
onboarding.create_project.enter_pat=Enter personal access token onboarding.create_project.enter_pat=Enter personal access token
onboarding.create_project.go_to_project=Go to project onboarding.create_project.go_to_project=Go to project
onboarding.create_project.github.title=Which GitHub repository do you want to setup? onboarding.create_project.github.title=Which GitHub repository do you want to setup?
onboarding.create_project.github.choose_organization=Choose organization onboarding.create_project.github.choose_organization=Choose organization
onboarding.create_project.github.warning.title=Could not connect to GitHub Enterprise
onboarding.create_project.github.warning.message=Please contact an administrator to configure GitHub Enterprise integration.
onboarding.create_project.github.warning.message_admin=Please make sure a GitHub Enterprise instance is configured in the {link} to create a new project from a repository.
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.
onboarding.create_project.github.warning.message_admin=Please make sure the GitHub instance is correctly configured in the {link} to create a new project from a repository.
onboarding.create_project.github.warning.message_admin.link=ALM integration settings onboarding.create_project.github.warning.message_admin.link=ALM integration settings
onboarding.create_project.github.no_orgs=We couldn't load any organizations with your key. Contact an administrator. onboarding.create_project.github.no_orgs=We couldn't load any organizations with your key. Contact an administrator.
onboarding.create_project.github.no_orgs_admin=We couldn't load any organizations with your key. Check the GitHub Enterprise instance configured in the {link}.
onboarding.create_project.github.no_orgs_admin=We couldn't load any organizations. Make sure the GitHub App is installed in at least one organization and check the GitHub instance configuration in the {link}.


onboarding.create_organization.page.header=Create Organization onboarding.create_organization.page.header=Create Organization
onboarding.create_organization.page.description=An organization is a space where a team or a whole company can collaborate accross many projects. onboarding.create_organization.page.description=An organization is a space where a team or a whole company can collaborate accross many projects.

+ 14
- 0
sonar-ws/src/main/protobuf/ws-alm_integrations.proto View File

optional string key = 1; optional string key = 1;
optional string name = 2; optional string name = 2;
} }

// WS api/alm_integrations/list_github_enterprise_repositories
message ListGithubEnterpriseRepositoriesWsResponse {
optional sonarqube.ws.commons.Paging paging = 1;
repeated GithubEnterpriseRepository repositories = 2;
}

message GithubEnterpriseRepository {
optional int64 id = 1;
optional string key = 2;
optional string name = 3;
optional string url = 4;
optional string sqProjectKey = 5;
}

Loading…
Cancel
Save