.then(({ repositories, paging }) => ({ projects: repositories, projectsPaging: paging }))
.catch(throwGlobalError);
}
+
+export function importGitlabProject(data: {
+ almSetting: string;
+ gitlabProjectId: string;
+}): Promise<{ project: ProjectBase }> {
+ const { almSetting, gitlabProjectId } = data;
+ return postJSON('/api/alm_integrations/import_gitlab_project', {
+ almSetting,
+ gitlabProjectId
+ }).catch(throwGlobalError);
+}
import {
checkPersonalAccessTokenIsValid,
getGitlabProjects,
+ importGitlabProject,
setAlmPersonalAccessToken
} from '../../../api/alm-integrations';
import { GitlabProject } from '../../../types/alm-integration';
}
interface State {
+ importingGitlabProjectId?: string;
loading: boolean;
loadingMore: boolean;
projects?: GitlabProject[];
}).catch(() => undefined);
};
+ handleImport = async (gitlabProjectId: string) => {
+ const { settings } = this.state;
+
+ if (!settings) {
+ return;
+ }
+
+ this.setState({ importingGitlabProjectId: gitlabProjectId });
+
+ const result = await importGitlabProject({
+ almSetting: settings.key,
+ gitlabProjectId
+ }).catch(() => undefined);
+
+ if (this.mounted) {
+ this.setState({ importingGitlabProjectId: undefined });
+
+ if (result) {
+ this.props.onProjectCreate([result.project.key]);
+ }
+ }
+ };
+
handleLoadMore = async () => {
this.setState({ loadingMore: true });
render() {
const { canAdmin, loadingBindings, location } = this.props;
const {
+ importingGitlabProjectId,
loading,
loadingMore,
projects,
<GitlabProjectCreateRenderer
settings={settings}
canAdmin={canAdmin}
+ importingGitlabProjectId={importingGitlabProjectId}
loading={loading || loadingBindings}
loadingMore={loadingMore}
+ onImport={this.handleImport}
onLoadMore={this.handleLoadMore}
onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate}
onSearch={this.handleSearch}
export interface GitlabProjectCreateRendererProps {
canAdmin?: boolean;
+ importingGitlabProjectId?: string;
loading: boolean;
loadingMore: boolean;
+ onImport: (gitlabProjectId: string) => void;
onLoadMore: () => void;
onPersonalAccessTokenCreate: (pat: string) => void;
onSearch: (searchQuery: string) => void;
export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRendererProps) {
const {
canAdmin,
+ importingGitlabProjectId,
loading,
loadingMore,
projects,
/>
) : (
<GitlabProjectSelectionForm
+ importingGitlabProjectId={importingGitlabProjectId}
loadingMore={loadingMore}
+ onImport={props.onImport}
onLoadMore={props.onLoadMore}
onSearch={props.onSearch}
projects={projects}
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
+import { Button } from 'sonar-ui-common/components/controls/buttons';
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 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 DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls';
import { GitlabProject } from '../../../types/alm-integration';
import { CreateProjectModes } from './types';
export interface GitlabProjectSelectionFormProps {
+ importingGitlabProjectId?: string;
loadingMore: boolean;
+ onImport: (gitlabProjectId: string) => void;
onLoadMore: () => void;
onSearch: (searchQuery: string) => void;
projects?: GitlabProject[];
}
export default function GitlabProjectSelectionForm(props: GitlabProjectSelectionFormProps) {
- const { loadingMore, projects = [], projectsPaging, searching, searchQuery } = props;
+ const {
+ importingGitlabProjectId,
+ loadingMore,
+ projects = [],
+ projectsPaging,
+ searching,
+ searchQuery
+ } = props;
if (projects.length === 0 && searchQuery.length === 0 && !searching) {
return (
</td>
</>
) : (
- <td colSpan={2}> </td>
+ <td colSpan={2} className="text-right">
+ <Button
+ disabled={!!importingGitlabProjectId}
+ onClick={() => props.onImport(project.id)}>
+ {translate('onboarding.create_project.gitlab.set_up')}
+ {importingGitlabProjectId === project.id && (
+ <DeferredSpinner className="spacer-left" />
+ )}
+ </Button>
+ </td>
)}
</tr>
))}
import {
checkPersonalAccessTokenIsValid,
getGitlabProjects,
+ importGitlabProject,
setAlmPersonalAccessToken
} from '../../../../api/alm-integrations';
import { mockGitlabProject } from '../../../../helpers/mocks/alm-integrations';
jest.mock('../../../../api/alm-integrations', () => ({
checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue(true),
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null),
- getGitlabProjects: jest.fn().mockRejectedValue('error')
+ getGitlabProjects: jest.fn().mockRejectedValue('error'),
+ importGitlabProject: jest.fn().mockRejectedValue('error')
}));
beforeEach(jest.clearAllMocks);
expect(getGitlabProjects).toBeCalledWith(expect.objectContaining({ query }));
});
+it('should import', async () => {
+ (checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(true);
+
+ const projects = [mockGitlabProject({ id: '1' }), mockGitlabProject({ id: '2' })];
+ (getGitlabProjects as jest.Mock).mockResolvedValueOnce({
+ projects,
+ projectsPaging: {
+ pageIndex: 1,
+ pageSize: 6,
+ total: 2
+ }
+ });
+ const createdProjectkey = 'imported_project_key';
+
+ (importGitlabProject as jest.Mock).mockResolvedValueOnce({
+ project: { key: createdProjectkey }
+ });
+
+ const onProjectCreate = jest.fn();
+
+ const wrapper = shallowRender({ onProjectCreate });
+ await waitAndUpdate(wrapper);
+
+ wrapper.instance().handleImport(projects[1].id);
+ expect(wrapper.state().importingGitlabProjectId).toBe(projects[1].id);
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().importingGitlabProjectId).toBeUndefined();
+ expect(onProjectCreate).toBeCalledWith([createdProjectkey]);
+});
+
+it('should do nothing with missing settings', async () => {
+ const wrapper = shallowRender({ settings: [] });
+
+ await waitAndUpdate(wrapper);
+
+ wrapper.instance().handleLoadMore();
+ wrapper.instance().handleSearch('whatever');
+ wrapper.instance().handlePersonalAccessTokenCreate('token');
+ wrapper.instance().handleImport('gitlab project id');
+
+ expect(checkPersonalAccessTokenIsValid).not.toHaveBeenCalled();
+ expect(getGitlabProjects).not.toHaveBeenCalled();
+ expect(importGitlabProject).not.toHaveBeenCalled();
+ expect(setAlmPersonalAccessToken).not.toHaveBeenCalled();
+});
+
function shallowRender(props: Partial<GitlabProjectCreate['props']> = {}) {
return shallow<GitlabProjectCreate>(
<GitlabProjectCreate
canAdmin={false}
loading={false}
loadingMore={false}
+ onImport={jest.fn()}
onLoadMore={jest.fn()}
onPersonalAccessTokenCreate={jest.fn()}
onSearch={jest.fn()}
import { shallow } from 'enzyme';
import * as React from 'react';
+import { Button } from 'sonar-ui-common/components/controls/buttons';
+import ListFooter from 'sonar-ui-common/components/controls/ListFooter';
+import SearchBox from 'sonar-ui-common/components/controls/SearchBox';
import { mockGitlabProject } from '../../../../helpers/mocks/alm-integrations';
import GitlabProjectSelectionForm, {
GitlabProjectSelectionFormProps
expect(
shallowRender({ projects: [], projectsPaging: mockPaging(), searchQuery: 'findme' })
).toMatchSnapshot('no projects when searching');
+
+ expect(shallowRender({ importingGitlabProjectId: '2' })).toMatchSnapshot('importing');
+});
+
+describe('appropriate callback', () => {
+ const onImport = jest.fn();
+ const onLoadMore = jest.fn();
+ const onSearch = jest.fn();
+ const wrapper = shallowRender({ onImport, onLoadMore, onSearch });
+
+ it('should be called when clicking to import', () => {
+ wrapper
+ .find(Button)
+ .first()
+ .simulate('click');
+
+ expect(onImport).toBeCalled();
+ });
+
+ it('should be assigned to the list footer', () => {
+ const { loadMore } = wrapper
+ .find(ListFooter)
+ .first()
+ .props();
+
+ expect(loadMore).toBe(onLoadMore);
+ });
+
+ it('should be assigned to the search box', () => {
+ const { onChange } = wrapper
+ .find(SearchBox)
+ .first()
+ .props();
+
+ expect(onChange).toBe(onSearch);
+ });
});
function shallowRender(props: Partial<GitlabProjectSelectionFormProps> = {}) {
return shallow<GitlabProjectSelectionFormProps>(
<GitlabProjectSelectionForm
loadingMore={false}
+ onImport={jest.fn()}
onLoadMore={jest.fn()}
onSearch={jest.fn()}
projects={projects}
canAdmin={false}
loading={true}
loadingMore={false}
+ onImport={[Function]}
onLoadMore={[Function]}
onPersonalAccessTokenCreate={[Function]}
onSearch={[Function]}
/>
<GitlabProjectSelectionForm
loadingMore={false}
+ onImport={[MockFunction]}
onLoadMore={[MockFunction]}
onSearch={[MockFunction]}
projectsPaging={
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`should render correctly: importing 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
+ className="text-right"
+ colSpan={2}
+ >
+ <Button
+ disabled={true}
+ onClick={[Function]}
+ >
+ onboarding.create_project.gitlab.set_up
+ </Button>
+ </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: no projects 1`] = `
<Alert
className="spacer-top"
</a>
</td>
<td
+ className="text-right"
colSpan={2}
>
-
+ <Button
+ disabled={false}
+ onClick={[Function]}
+ >
+ onboarding.create_project.gitlab.set_up
+ </Button>
</td>
</tr>
<tr
onboarding.create_project.only_showing_X_first_repos=We're only displaying the first {0} repositories. If you're looking for a repository that's not in this list, use the search above.
onboarding.create_project.import_selected_repo=Set up selected repository
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 set up?
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.
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_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.title=Which GitLab project do you want to set up?
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_project.gitlab.set_up=Set up
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.