Bladeren bron

SONAR-13629 Display gitlab projects

tags/8.5.0.37579
Jeremy Davis 3 jaren geleden
bovenliggende
commit
6e7ab27398
18 gewijzigde bestanden met toevoegingen van 851 en 40 verwijderingen
  1. 24
    6
      server/sonar-web/src/main/js/api/alm-integrations.ts
  2. 1
    0
      server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
  3. 2
    2
      server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx
  4. 131
    25
      server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx
  5. 23
    1
      server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx
  6. 149
    0
      server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx
  7. 2
    2
      server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx
  8. 102
    3
      server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx
  9. 10
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx
  10. 68
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectSelectionForm-test.tsx
  11. 13
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
  12. 12
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap
  13. 34
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreateRenderer-test.tsx.snap
  14. 234
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap
  15. 17
    0
      server/sonar-web/src/main/js/apps/create/project/style.css
  16. 15
    1
      server/sonar-web/src/main/js/helpers/mocks/alm-integrations.ts
  17. 11
    0
      server/sonar-web/src/main/js/types/alm-integration.ts
  18. 3
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 24
- 6
server/sonar-web/src/main/js/api/alm-integrations.ts Bestand weergeven

@@ -23,7 +23,8 @@ import {
BitbucketProject,
BitbucketRepository,
GithubOrganization,
GithubRepository
GithubRepository,
GitlabProject
} from '../types/alm-integration';
import { ProjectBase } from './components';

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

export function getGitlabProjects(data: {
almSetting: string;
page?: number;
pageSize?: number;
query?: string;
}): Promise<{ projects: GitlabProject[]; projectsPaging: T.Paging }> {
const { almSetting, pageSize, page, query } = data;
return getJSON('/api/alm_integrations/search_gitlab_repos', {
almSetting,
projectName: query || undefined,
p: page,
ps: pageSize
})
.then(({ repositories, paging }) => ({ projects: repositories, projectsPaging: paging }))
.catch(throwGlobalError);
}

+ 1
- 0
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx Bestand weergeven

@@ -138,6 +138,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> {
loadingBindings={loading}
location={location}
onProjectCreate={this.handleProjectCreate}
router={router}
settings={gitlabSettings}
/>
);

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx Bestand weergeven

@@ -176,8 +176,8 @@ export default class GitHubProjectCreate extends React.Component<Props, State> {
const data = await getGithubRepositories({
almSetting: settings.key,
organization: organizationKey,
ps: REPOSITORY_PAGE_SIZE,
p: page,
pageSize: REPOSITORY_PAGE_SIZE,
page,
query
});


+ 131
- 25
server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx Bestand weergeven

@@ -21,12 +21,14 @@ import * as React from 'react';
import { WithRouterProps } from 'react-router';
import {
checkPersonalAccessTokenIsValid,
getGitlabProjects,
setAlmPersonalAccessToken
} from '../../../api/alm-integrations';
import { GitlabProject } from '../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../types/alm-settings';
import GitlabProjectCreateRenderer from './GitlabProjectCreateRenderer';

interface Props extends Pick<WithRouterProps, 'location'> {
interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
canAdmin: boolean;
loadingBindings: boolean;
onProjectCreate: (projectKeys: string[]) => void;
@@ -35,20 +37,32 @@ interface Props extends Pick<WithRouterProps, 'location'> {

interface State {
loading: boolean;
loadingMore: boolean;
projects?: GitlabProject[];
projectsPaging: T.Paging;
submittingToken: boolean;
tokenIsValid: boolean;
tokenValidationFailed: boolean;
searching: boolean;
searchQuery: string;
settings?: AlmSettingsInstance;
}

const GITLAB_PROJECTS_PAGESIZE = 30;

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

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

this.state = {
loading: false,
loadingMore: false,
projectsPaging: { pageIndex: 1, total: 0, pageSize: GITLAB_PROJECTS_PAGESIZE },
tokenIsValid: false,
searching: false,
searchQuery: '',
settings: props.settings.length === 1 ? props.settings[0] : undefined,
submittingToken: false,
tokenValidationFailed: false
@@ -78,11 +92,27 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat

const tokenIsValid = await this.checkPersonalAccessToken();

let result;
if (tokenIsValid) {
result = await this.fetchProjects();
}

if (this.mounted) {
this.setState({
tokenIsValid,
loading: false
});
if (result) {
const { projects, projectsPaging } = result;

this.setState({
tokenIsValid,
loading: false,
projects,
projectsPaging
});
} else {
this.setState({
tokenIsValid,
loading: false
});
}
}
};

@@ -96,7 +126,61 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat
return checkPersonalAccessTokenIsValid(settings.key).catch(() => false);
};

handlePersonalAccessTokenCreate = (token: string) => {
fetchProjects = (pageIndex = 1, query?: string) => {
const { settings } = this.state;

if (!settings) {
return Promise.resolve(undefined);
}

return getGitlabProjects({
almSetting: settings.key,
page: pageIndex,
pageSize: GITLAB_PROJECTS_PAGESIZE,
query
}).catch(() => undefined);
};

handleLoadMore = async () => {
this.setState({ loadingMore: true });

const {
projectsPaging: { pageIndex },
searchQuery
} = this.state;

const result = await this.fetchProjects(pageIndex + 1, searchQuery);

if (this.mounted) {
this.setState(({ projects = [], projectsPaging }) => ({
loadingMore: false,
projects: result ? [...projects, ...result.projects] : projects,
projectsPaging: result ? result.projectsPaging : projectsPaging
}));
}
};

handleSearch = async (searchQuery: string) => {
this.setState({ searching: true, searchQuery });

const result = await this.fetchProjects(1, searchQuery);

if (this.mounted) {
this.setState(({ projects, projectsPaging }) => ({
searching: false,
projects: result ? result.projects : projects,
projectsPaging: result ? result.projectsPaging : projectsPaging
}));
}
};

cleanUrl = () => {
const { location, router } = this.props;
delete location.query.resetPat;
router.replace(location);
};

handlePersonalAccessTokenCreate = async (token: string) => {
const { settings } = this.state;

if (!settings || token.length < 1) {
@@ -104,37 +188,59 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat
}

this.setState({ submittingToken: true, tokenValidationFailed: false });
setAlmPersonalAccessToken(settings.key, token)
.then(this.checkPersonalAccessToken)
.then(patIsValid => {
if (this.mounted) {
this.setState({
submittingToken: false,
tokenIsValid: patIsValid,
tokenValidationFailed: !patIsValid
});
if (patIsValid) {
this.fetchInitialData();
}
}
})
.catch(() => {
if (this.mounted) {
this.setState({ submittingToken: false });

try {
await setAlmPersonalAccessToken(settings.key, token);

const patIsValid = await this.checkPersonalAccessToken();

if (this.mounted) {
this.setState({
submittingToken: false,
tokenIsValid: patIsValid,
tokenValidationFailed: !patIsValid
});

if (patIsValid) {
this.cleanUrl();
await this.fetchInitialData();
}
});
}
} catch (e) {
if (this.mounted) {
this.setState({ submittingToken: false });
}
}
};

render() {
const { canAdmin, loadingBindings, location } = this.props;
const { loading, tokenIsValid, settings, submittingToken, tokenValidationFailed } = this.state;
const {
loading,
loadingMore,
projects,
projectsPaging,
tokenIsValid,
searching,
searchQuery,
settings,
submittingToken,
tokenValidationFailed
} = this.state;

return (
<GitlabProjectCreateRenderer
settings={settings}
canAdmin={canAdmin}
loading={loading || loadingBindings}
loadingMore={loadingMore}
onLoadMore={this.handleLoadMore}
onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate}
onSearch={this.handleSearch}
projects={projects}
projectsPaging={projectsPaging}
searching={searching}
searchQuery={searchQuery}
showPersonalAccessTokenForm={!tokenIsValid || Boolean(location.query.resetPat)}
submittingToken={submittingToken}
tokenValidationFailed={tokenValidationFailed}

+ 23
- 1
server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx Bestand weergeven

@@ -20,15 +20,24 @@
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
import { GitlabProject } from '../../../types/alm-integration';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
import CreateProjectPageHeader from './CreateProjectPageHeader';
import GitlabProjectSelectionForm from './GitlabProjectSelectionForm';
import PersonalAccessTokenForm from './PersonalAccessTokenForm';
import WrongBindingCountAlert from './WrongBindingCountAlert';

export interface GitlabProjectCreateRendererProps {
canAdmin?: boolean;
loading: boolean;
loadingMore: boolean;
onLoadMore: () => void;
onPersonalAccessTokenCreate: (pat: string) => void;
onSearch: (searchQuery: string) => void;
projects?: GitlabProject[];
projectsPaging: T.Paging;
searching: boolean;
searchQuery: string;
settings?: AlmSettingsInstance;
showPersonalAccessTokenForm?: boolean;
submittingToken?: boolean;
@@ -39,6 +48,11 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe
const {
canAdmin,
loading,
loadingMore,
projects,
projectsPaging,
searching,
searchQuery,
settings,
showPersonalAccessTokenForm,
submittingToken,
@@ -77,7 +91,15 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe
validationFailed={tokenValidationFailed}
/>
) : (
<div>Token is valid!</div>
<GitlabProjectSelectionForm
loadingMore={loadingMore}
onLoadMore={props.onLoadMore}
onSearch={props.onSearch}
projects={projects}
projectsPaging={projectsPaging}
searching={searching}
searchQuery={searchQuery}
/>
))}
</>
);

+ 149
- 0
server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx Bestand weergeven

@@ -0,0 +1,149 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import ListFooter from 'sonar-ui-common/components/controls/ListFooter';
import SearchBox from 'sonar-ui-common/components/controls/SearchBox';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import CheckIcon from 'sonar-ui-common/components/icons/CheckIcon';
import DetachIcon from 'sonar-ui-common/components/icons/DetachIcon';
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls';
import { GitlabProject } from '../../../types/alm-integration';
import { ComponentQualifier } from '../../../types/component';
import { CreateProjectModes } from './types';

export interface GitlabProjectSelectionFormProps {
loadingMore: boolean;
onLoadMore: () => void;
onSearch: (searchQuery: string) => void;
projects?: GitlabProject[];
projectsPaging: T.Paging;
searching: boolean;
searchQuery: string;
}

export default function GitlabProjectSelectionForm(props: GitlabProjectSelectionFormProps) {
const { loadingMore, projects = [], projectsPaging, searching, searchQuery } = props;

if (projects.length === 0 && searchQuery.length === 0 && !searching) {
return (
<Alert className="spacer-top" variant="warning">
<FormattedMessage
defaultMessage={translate('onboarding.create_project.gitlab.no_projects')}
id="onboarding.create_project.gitlab.no_projects"
values={{
link: (
<Link
to={{
pathname: '/projects/create',
query: { mode: CreateProjectModes.GitLab, resetPat: 1 }
}}>
{translate('onboarding.create_project.update_your_token')}
</Link>
)
}}
/>
</Alert>
);
}

return (
<div className="boxed-group big-padded create-project-import-gitlab">
<SearchBox
className="spacer"
loading={searching}
minLength={3}
onChange={props.onSearch}
placeholder={translate('onboarding.create_project.gitlab.search_prompt')}
/>

<hr />

{projects.length === 0 ? (
<div className="padded">{translate('no_results')}</div>
) : (
<table className="data zebra zebra-hover">
<tbody>
{projects.map(project => (
<tr key={project.id}>
<td>
<Tooltip overlay={project.slug}>
<strong className="project-name display-inline-block text-ellipsis">
{project.name}
</strong>
</Tooltip>
<br />
<Tooltip overlay={project.pathSlug}>
<span className="text-muted project-path display-inline-block text-ellipsis">
{project.pathName}
</span>
</Tooltip>
</td>
<td>
<a
className="display-inline-flex-center big-spacer-right"
href={project.url}
rel="noopener noreferrer"
target="_blank">
<DetachIcon className="little-spacer-right" />
{translate('onboarding.create_project.gitlab.link')}
</a>
</td>
{project.sqProjectKey ? (
<>
<td>
<span className="display-flex-center display-flex-justify-end already-set-up">
<CheckIcon className="little-spacer-right" size={12} />
{translate('onboarding.create_project.repository_imported')}:
</span>
</td>
<td>
<div className="sq-project-link text-ellipsis">
<Link to={getProjectUrl(project.sqProjectKey)}>
<QualifierIcon
className="spacer-right"
qualifier={ComponentQualifier.Project}
/>
{project.sqProjectName}
</Link>
</div>
</td>
</>
) : (
<td colSpan={2}>&nbsp;</td>
)}
</tr>
))}
</tbody>
</table>
)}
<ListFooter
count={projects.length}
loadMore={props.onLoadMore}
loading={loadingMore}
total={projectsPaging.total}
/>
</div>
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx Bestand weergeven

@@ -186,8 +186,8 @@ it('should handle search', async () => {
expect(getGithubRepositories).toBeCalledWith({
almSetting: 'a',
organization: 'o1',
p: 1,
ps: 30,
page: 1,
pageSize: 30,
query: 'query'
});
expect(wrapper.state().repositories).toEqual(repositories);

+ 102
- 3
server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx Bestand weergeven

@@ -23,16 +23,19 @@ import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import {
checkPersonalAccessTokenIsValid,
getGitlabProjects,
setAlmPersonalAccessToken
} from '../../../../api/alm-integrations';
import { mockGitlabProject } from '../../../../helpers/mocks/alm-integrations';
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { mockLocation } from '../../../../helpers/testMocks';
import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
import { AlmKeys } from '../../../../types/alm-settings';
import GitlabProjectCreate from '../GitlabProjectCreate';

jest.mock('../../../../api/alm-integrations', () => ({
checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue(true),
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null)
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null),
getGitlabProjects: jest.fn().mockRejectedValue('error')
}));

beforeEach(jest.clearAllMocks);
@@ -75,7 +78,12 @@ it('should correctly handle an invalid PAT', async () => {
});

describe('setting a new PAT', () => {
const wrapper = shallowRender();
const routerReplace = jest.fn();
const wrapper = shallowRender({ router: mockRouter({ replace: routerReplace }) });

beforeEach(() => {
jest.clearAllMocks();
});

it('should correctly handle it if invalid', async () => {
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(false);
@@ -99,9 +107,99 @@ describe('setting a new PAT', () => {
expect(checkPersonalAccessTokenIsValid).toBeCalled();
expect(wrapper.state().submittingToken).toBe(false);
expect(wrapper.state().tokenValidationFailed).toBe(false);

expect(routerReplace).toBeCalled();
});
});

it('should fetch more projects and preserve search', async () => {
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(true);

const projects = [
mockGitlabProject({ id: '1' }),
mockGitlabProject({ id: '2' }),
mockGitlabProject({ id: '3' }),
mockGitlabProject({ id: '4' }),
mockGitlabProject({ id: '5' }),
mockGitlabProject({ id: '6' })
];
(getGitlabProjects as jest.Mock)
.mockResolvedValueOnce({
projects: projects.slice(0, 5),
projectsPaging: {
pageIndex: 1,
pageSize: 4,
total: 6
}
})
.mockResolvedValueOnce({
projects: projects.slice(5),
projectsPaging: {
pageIndex: 2,
pageSize: 4,
total: 6
}
});

const wrapper = shallowRender();

await waitAndUpdate(wrapper);
wrapper.setState({ searchQuery: 'query' });

wrapper.instance().handleLoadMore();
expect(wrapper.state().loadingMore).toBe(true);

await waitAndUpdate(wrapper);
expect(wrapper.state().loadingMore).toBe(false);
expect(wrapper.state().projects).toEqual(projects);

expect(getGitlabProjects).toBeCalledWith(expect.objectContaining({ query: 'query' }));
});

it('should search for projects', async () => {
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(true);

const projects = [
mockGitlabProject({ id: '1' }),
mockGitlabProject({ id: '2' }),
mockGitlabProject({ id: '3' }),
mockGitlabProject({ id: '4' }),
mockGitlabProject({ id: '5' }),
mockGitlabProject({ id: '6' })
];
(getGitlabProjects as jest.Mock)
.mockResolvedValueOnce({
projects,
projectsPaging: {
pageIndex: 1,
pageSize: 6,
total: 6
}
})
.mockResolvedValueOnce({
projects: projects.slice(3, 5),
projectsPaging: {
pageIndex: 1,
pageSize: 6,
total: 2
}
});
const query = 'query';

const wrapper = shallowRender();
await waitAndUpdate(wrapper);

wrapper.instance().handleSearch(query);
expect(wrapper.state().searching).toBe(true);

await waitAndUpdate(wrapper);
expect(wrapper.state().searching).toBe(false);
expect(wrapper.state().searchQuery).toBe(query);
expect(wrapper.state().projects).toEqual([projects[3], projects[4]]);

expect(getGitlabProjects).toBeCalledWith(expect.objectContaining({ query }));
});

function shallowRender(props: Partial<GitlabProjectCreate['props']> = {}) {
return shallow<GitlabProjectCreate>(
<GitlabProjectCreate
@@ -109,6 +207,7 @@ function shallowRender(props: Partial<GitlabProjectCreate['props']> = {}) {
loadingBindings={false}
location={mockLocation()}
onProjectCreate={jest.fn()}
router={mockRouter()}
settings={[mockAlmSettingsInstance({ alm: AlmKeys.GitLab, key: almSettingKey })]}
{...props}
/>

+ 10
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx Bestand weergeven

@@ -33,6 +33,9 @@ it('should render correctly', () => {
'invalid settings, admin user'
);
expect(shallowRender()).toMatchSnapshot('pat form');
expect(shallowRender({ showPersonalAccessTokenForm: false })).toMatchSnapshot(
'project selection form'
);
});

function shallowRender(props: Partial<GitlabProjectCreateRendererProps> = {}) {
@@ -40,7 +43,14 @@ function shallowRender(props: Partial<GitlabProjectCreateRendererProps> = {}) {
<GitlabProjectCreateRenderer
canAdmin={false}
loading={false}
loadingMore={false}
onLoadMore={jest.fn()}
onPersonalAccessTokenCreate={jest.fn()}
onSearch={jest.fn()}
projects={undefined}
projectsPaging={{ pageIndex: 1, pageSize: 30, total: 0 }}
searching={false}
searchQuery=""
showPersonalAccessTokenForm={true}
submittingToken={false}
tokenValidationFailed={false}

+ 68
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectSelectionForm-test.tsx Bestand weergeven

@@ -0,0 +1,68 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { shallow } from 'enzyme';
import * as React from 'react';
import { mockGitlabProject } from '../../../../helpers/mocks/alm-integrations';
import GitlabProjectSelectionForm, {
GitlabProjectSelectionFormProps
} from '../GitlabProjectSelectionForm';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('projects');

expect(shallowRender({ projects: undefined, projectsPaging: mockPaging() })).toMatchSnapshot(
'undefined projects'
);
expect(shallowRender({ projects: [], projectsPaging: mockPaging() })).toMatchSnapshot(
'no projects'
);
expect(
shallowRender({ projects: [], projectsPaging: mockPaging(), searchQuery: 'findme' })
).toMatchSnapshot('no projects when searching');
});

function shallowRender(props: Partial<GitlabProjectSelectionFormProps> = {}) {
const projects = [
mockGitlabProject(),
mockGitlabProject({
id: '2',
sqProjectKey: 'already-imported',
sqProjectName: 'Already Imported'
})
];

return shallow<GitlabProjectSelectionFormProps>(
<GitlabProjectSelectionForm
loadingMore={false}
onLoadMore={jest.fn()}
onSearch={jest.fn()}
projects={projects}
projectsPaging={mockPaging(projects.length)}
searching={false}
searchQuery=""
{...props}
/>
);
}

function mockPaging(total = 0) {
return { total, pageIndex: 1, pageSize: 30 };
}

+ 13
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap Bestand weergeven

@@ -174,6 +174,19 @@ exports[`should render correctly if the GitLab method is selected 1`] = `
}
}
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>

+ 12
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap Bestand weergeven

@@ -4,7 +4,19 @@ exports[`should render correctly 1`] = `
<GitlabProjectCreateRenderer
canAdmin={false}
loading={true}
loadingMore={false}
onLoadMore={[Function]}
onPersonalAccessTokenCreate={[Function]}
onSearch={[Function]}
projectsPaging={
Object {
"pageIndex": 1,
"pageSize": 30,
"total": 0,
}
}
searchQuery=""
searching={false}
settings={
Object {
"alm": "gitlab",

+ 34
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreateRenderer-test.tsx.snap Bestand weergeven

@@ -101,3 +101,37 @@ exports[`should render correctly: pat form 1`] = `
/>
</Fragment>
`;

exports[`should render correctly: project selection form 1`] = `
<Fragment>
<CreateProjectPageHeader
title={
<span
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="24"
src="/images/alm/gitlab.svg"
/>
onboarding.create_project.gitlab.title
</span>
}
/>
<GitlabProjectSelectionForm
loadingMore={false}
onLoadMore={[MockFunction]}
onSearch={[MockFunction]}
projectsPaging={
Object {
"pageIndex": 1,
"pageSize": 30,
"total": 0,
}
}
searchQuery=""
searching={false}
/>
</Fragment>
`;

+ 234
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap Bestand weergeven

@@ -0,0 +1,234 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: no projects 1`] = `
<Alert
className="spacer-top"
variant="warning"
>
<FormattedMessage
defaultMessage="onboarding.create_project.gitlab.no_projects"
id="onboarding.create_project.gitlab.no_projects"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/projects/create",
"query": Object {
"mode": "gitlab",
"resetPat": 1,
},
}
}
>
onboarding.create_project.update_your_token
</Link>,
}
}
/>
</Alert>
`;

exports[`should render correctly: no projects when searching 1`] = `
<div
className="boxed-group big-padded create-project-import-gitlab"
>
<SearchBox
className="spacer"
loading={false}
minLength={3}
onChange={[MockFunction]}
placeholder="onboarding.create_project.gitlab.search_prompt"
/>
<hr />
<div
className="padded"
>
no_results
</div>
<ListFooter
count={0}
loadMore={[MockFunction]}
loading={false}
total={0}
/>
</div>
`;

exports[`should render correctly: projects 1`] = `
<div
className="boxed-group big-padded create-project-import-gitlab"
>
<SearchBox
className="spacer"
loading={false}
minLength={3}
onChange={[MockFunction]}
placeholder="onboarding.create_project.gitlab.search_prompt"
/>
<hr />
<table
className="data zebra zebra-hover"
>
<tbody>
<tr
key="id1234"
>
<td>
<Tooltip
overlay="awesome-project-exclamation"
>
<strong
className="project-name display-inline-block text-ellipsis"
>
Awesome Project !
</strong>
</Tooltip>
<br />
<Tooltip
overlay="company/best-projects"
>
<span
className="text-muted project-path display-inline-block text-ellipsis"
>
Company / Best Projects
</span>
</Tooltip>
</td>
<td>
<a
className="display-inline-flex-center big-spacer-right"
href="https://gitlab.company.com/best-projects/awesome-project-exclamation"
rel="noopener noreferrer"
target="_blank"
>
<DetachIcon
className="little-spacer-right"
/>
onboarding.create_project.gitlab.link
</a>
</td>
<td
colSpan={2}
>
 
</td>
</tr>
<tr
key="2"
>
<td>
<Tooltip
overlay="awesome-project-exclamation"
>
<strong
className="project-name display-inline-block text-ellipsis"
>
Awesome Project !
</strong>
</Tooltip>
<br />
<Tooltip
overlay="company/best-projects"
>
<span
className="text-muted project-path display-inline-block text-ellipsis"
>
Company / Best Projects
</span>
</Tooltip>
</td>
<td>
<a
className="display-inline-flex-center big-spacer-right"
href="https://gitlab.company.com/best-projects/awesome-project-exclamation"
rel="noopener noreferrer"
target="_blank"
>
<DetachIcon
className="little-spacer-right"
/>
onboarding.create_project.gitlab.link
</a>
</td>
<td>
<span
className="display-flex-center display-flex-justify-end already-set-up"
>
<CheckIcon
className="little-spacer-right"
size={12}
/>
onboarding.create_project.repository_imported
:
</span>
</td>
<td>
<div
className="sq-project-link text-ellipsis"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "already-imported",
},
}
}
>
<QualifierIcon
className="spacer-right"
qualifier="TRK"
/>
Already Imported
</Link>
</div>
</td>
</tr>
</tbody>
</table>
<ListFooter
count={2}
loadMore={[MockFunction]}
loading={false}
total={2}
/>
</div>
`;

exports[`should render correctly: undefined projects 1`] = `
<Alert
className="spacer-top"
variant="warning"
>
<FormattedMessage
defaultMessage="onboarding.create_project.gitlab.no_projects"
id="onboarding.create_project.gitlab.no_projects"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/projects/create",
"query": Object {
"mode": "gitlab",
"resetPat": 1,
},
}
}
>
onboarding.create_project.update_your_token
</Link>,
}
}
/>
</Alert>
`;

+ 17
- 0
server/sonar-web/src/main/js/apps/create/project/style.css Bestand weergeven

@@ -60,3 +60,20 @@
.create-project-github-repository .notice svg {
color: var(--green);
}

.create-project-import-gitlab table > tbody > tr > td {
vertical-align: middle;
}

.create-project-import-gitlab .project-name,
.create-project-import-gitlab .project-path {
max-width: 400px;
}

.create-project-import-gitlab .sq-project-link {
max-width: 300px;
}

.create-project-import-gitlab .already-set-up svg {
color: var(--green);
}

+ 15
- 1
server/sonar-web/src/main/js/helpers/mocks/alm-integrations.ts Bestand weergeven

@@ -20,7 +20,8 @@
import {
BitbucketProject,
BitbucketRepository,
GithubRepository
GithubRepository,
GitlabProject
} from '../../types/alm-integration';

export function mockBitbucketProject(overrides: Partial<BitbucketProject> = {}): BitbucketProject {
@@ -54,3 +55,16 @@ export function mockGitHubRepository(overrides: Partial<GithubRepository> = {}):
...overrides
};
}

export function mockGitlabProject(overrides: Partial<GitlabProject> = {}): GitlabProject {
return {
id: 'id1234',
name: 'Awesome Project !',
slug: 'awesome-project-exclamation',
pathName: 'Company / Best Projects',
pathSlug: 'company/best-projects',
sqProjectKey: '',
url: 'https://gitlab.company.com/best-projects/awesome-project-exclamation',
...overrides
};
}

+ 11
- 0
server/sonar-web/src/main/js/types/alm-integration.ts Bestand weergeven

@@ -48,3 +48,14 @@ export interface GithubRepository {
url: string;
sqProjectKey: string;
}

export interface GitlabProject {
id: string;
name: string;
pathName: string;
pathSlug: string;
sqProjectKey?: string;
sqProjectName?: string;
slug: string;
url: string;
}

+ 3
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Bestand weergeven

@@ -3172,6 +3172,9 @@ onboarding.create_project.github.warning.message_admin.link=ALM integration sett
onboarding.create_project.github.no_orgs=We couldn't load any organizations with your key. Contact an administrator.
onboarding.create_project.github.no_orgs_admin=We couldn't load any organizations. Make sure the GitHub App is installed in at least one organization and check the GitHub instance configuration in the {link}.
onboarding.create_project.gitlab.title=Which GitLab project do you want to setup?
onboarding.create_project.gitlab.no_projects=No projects could be fetched from Gitlab. Contact your system administrator, or {link}.
onboarding.create_project.gitlab.link=See on GitLab
onboarding.create_project.gitlab.search_prompt=Search for projects

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.

Laden…
Annuleren
Opslaan