aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/create/project
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2021-05-19 10:50:19 +0200
committersonartech <sonartech@sonarsource.com>2021-05-21 20:03:37 +0000
commit1a84279cce2f1ea0d83cbd7eb4f24088533fe619 (patch)
tree533aa39193b66c68bd7597ffc177b379c7a16331 /server/sonar-web/src/main/js/apps/create/project
parentbdbcff0bfa506c03d4952fda567fba8147ac01c6 (diff)
downloadsonarqube-1a84279cce2f1ea0d83cbd7eb4f24088533fe619.tar.gz
sonarqube-1a84279cce2f1ea0d83cbd7eb4f24088533fe619.zip
SONAR-14802 Adding bitbucket cloud repository search form
Diffstat (limited to 'server/sonar-web/src/main/js/apps/create/project')
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx117
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreateRender.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx171
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreate-test.tsx86
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreateRender-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudSearchForm-test.tsx72
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreate-test.tsx.snap22
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreateRender-test.tsx.snap11
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudSearchForm-test.tsx.snap370
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap16
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/style.css10
12 files changed, 881 insertions, 42 deletions
diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx
index ffd78a7a2f7..a9d9f6efe1b 100644
--- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx
@@ -19,7 +19,8 @@
*/
import * as React from 'react';
import { WithRouterProps } from 'react-router';
-import { BitbucketProjectRepositories, BitbucketRepository } from '../../../types/alm-integration';
+import { searchForBitbucketCloudRepositories } from '../../../api/alm-integrations';
+import { BitbucketCloudRepository, BitbucketRepository } from '../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../types/alm-settings';
import BitbucketCloudProjectCreateRenderer from './BitbucketCloudProjectCreateRender';
@@ -33,12 +34,19 @@ interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
interface State {
settings: AlmSettingsInstance;
loading: boolean;
- projectRepositories?: BitbucketProjectRepositories;
+ loadingMore: boolean;
+ isLastPage?: boolean;
+ projectsPaging: Omit<T.Paging, 'total'>;
+ repositories: BitbucketCloudRepository[];
searchResults?: BitbucketRepository[];
selectedRepository?: BitbucketRepository;
+ searching: boolean;
+ searchQuery: string;
showPersonalAccessTokenForm: boolean;
+ resetPat: boolean;
}
+export const BITBUCKET_PROJECTS_PAGESIZE = 30;
export default class BitbucketCloudProjectCreate extends React.PureComponent<Props, State> {
mounted = false;
@@ -49,13 +57,18 @@ export default class BitbucketCloudProjectCreate extends React.PureComponent<Pro
// one from the list.
settings: props.settings[0],
loading: false,
+ loadingMore: false,
+ resetPat: false,
+ projectsPaging: { pageIndex: 1, pageSize: BITBUCKET_PROJECTS_PAGESIZE },
+ repositories: [],
+ searching: false,
+ searchQuery: '',
showPersonalAccessTokenForm: true
};
}
componentDidMount() {
this.mounted = true;
- this.fetchData();
}
componentDidUpdate(prevProps: Props) {
@@ -76,18 +89,110 @@ export default class BitbucketCloudProjectCreate extends React.PureComponent<Pro
router.replace(location);
};
- async fetchData() {}
+ async fetchData(more = false) {
+ const {
+ settings,
+ searchQuery,
+ projectsPaging: { pageIndex, pageSize },
+ showPersonalAccessTokenForm
+ } = this.state;
+ if (settings && !showPersonalAccessTokenForm) {
+ const { isLastPage, repositories } = await searchForBitbucketCloudRepositories(
+ settings.key,
+ searchQuery,
+ pageSize,
+ pageIndex
+ ).catch(() => {
+ this.handleError();
+ return { isLastPage: undefined, repositories: undefined };
+ });
+ if (this.mounted && isLastPage !== undefined && repositories !== undefined) {
+ if (more) {
+ this.setState(state => ({
+ isLastPage,
+ repositories: [...state.repositories, ...repositories]
+ }));
+ } else {
+ this.setState({ isLastPage, repositories });
+ }
+ }
+ }
+ }
+
+ handleError = () => {
+ if (this.mounted) {
+ this.setState({
+ projectsPaging: { pageIndex: 1, pageSize: BITBUCKET_PROJECTS_PAGESIZE },
+ repositories: [],
+ resetPat: true,
+ showPersonalAccessTokenForm: true
+ });
+ }
+
+ return undefined;
+ };
+
+ handleSearch = (searchQuery: string) => {
+ this.setState(
+ {
+ searching: true,
+ projectsPaging: { pageIndex: 1, pageSize: BITBUCKET_PROJECTS_PAGESIZE },
+ searchQuery
+ },
+ async () => {
+ await this.fetchData();
+ if (this.mounted) {
+ this.setState({ searching: false });
+ }
+ }
+ );
+ };
+
+ handleLoadMore = () => {
+ this.setState(
+ state => ({
+ loadingMore: true,
+ projectsPaging: {
+ pageIndex: state.projectsPaging.pageIndex + 1,
+ pageSize: state.projectsPaging.pageSize
+ }
+ }),
+ async () => {
+ await this.fetchData(true);
+ if (this.mounted) {
+ this.setState({ loadingMore: false });
+ }
+ }
+ );
+ };
render() {
const { canAdmin, loadingBindings, location } = this.props;
- const { settings, loading, showPersonalAccessTokenForm } = this.state;
+ const {
+ isLastPage = true,
+ settings,
+ loading,
+ loadingMore,
+ repositories,
+ showPersonalAccessTokenForm,
+ resetPat,
+ searching,
+ searchQuery
+ } = this.state;
return (
<BitbucketCloudProjectCreateRenderer
+ isLastPage={isLastPage}
settings={settings}
canAdmin={canAdmin}
+ loadingMore={loadingMore}
loading={loading || loadingBindings}
+ onLoadMore={this.handleLoadMore}
onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated}
- resetPat={Boolean(location.query.resetPat)}
+ onSearch={this.handleSearch}
+ repositories={repositories}
+ searching={searching}
+ searchQuery={searchQuery}
+ resetPat={resetPat || Boolean(location.query.resetPat)}
showPersonalAccessTokenForm={
showPersonalAccessTokenForm || Boolean(location.query.resetPat)
}
diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreateRender.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreateRender.tsx
index a3c50b0dc0f..dc68ee2589f 100644
--- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreateRender.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreateRender.tsx
@@ -20,24 +20,44 @@
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
+import { BitbucketCloudRepository } from '../../../types/alm-integration';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
+import BitbucketCloudSearchForm from './BitbucketCloudSearchForm';
import CreateProjectPageHeader from './CreateProjectPageHeader';
import PersonalAccessTokenForm from './PersonalAccessTokenForm';
import WrongBindingCountAlert from './WrongBindingCountAlert';
export interface BitbucketCloudProjectCreateRendererProps {
+ isLastPage: boolean;
settings?: AlmSettingsInstance;
canAdmin?: boolean;
loading: boolean;
+ loadingMore: boolean;
+ onLoadMore: () => void;
onPersonalAccessTokenCreated: () => void;
+ onSearch: (searchQuery: string) => void;
+ repositories?: BitbucketCloudRepository[];
resetPat: boolean;
+ searching: boolean;
+ searchQuery: string;
showPersonalAccessTokenForm: boolean;
}
export default function BitbucketCloudProjectCreateRenderer(
props: BitbucketCloudProjectCreateRendererProps
) {
- const { settings, canAdmin, loading, resetPat, showPersonalAccessTokenForm } = props;
+ const {
+ isLastPage,
+ settings,
+ canAdmin,
+ loading,
+ loadingMore,
+ repositories,
+ resetPat,
+ searching,
+ searchQuery,
+ showPersonalAccessTokenForm
+ } = props;
return (
<>
@@ -69,7 +89,15 @@ export default function BitbucketCloudProjectCreateRenderer(
onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated}
/>
) : (
- <p>Placeholder for next step</p>
+ <BitbucketCloudSearchForm
+ isLastPage={isLastPage}
+ loadingMore={loadingMore}
+ searchQuery={searchQuery}
+ searching={searching}
+ onSearch={props.onSearch}
+ onLoadMore={props.onLoadMore}
+ repositories={repositories}
+ />
))}
</>
);
diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx
new file mode 100644
index 00000000000..a01de30bac4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx
@@ -0,0 +1,171 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { Button } from 'sonar-ui-common/components/controls/buttons';
+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 DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
+import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
+import { formatMeasure } from 'sonar-ui-common/helpers/measures';
+import { getProjectUrl } from '../../../helpers/urls';
+import { BitbucketCloudRepository } from '../../../types/alm-integration';
+import { ComponentQualifier } from '../../../types/component';
+import { CreateProjectModes } from './types';
+
+export interface BitbucketCloudSearchFormProps {
+ isLastPage: boolean;
+ loadingMore: boolean;
+ onLoadMore: () => void;
+ onSearch: (searchQuery: string) => void;
+ repositories?: BitbucketCloudRepository[];
+ searching: boolean;
+ searchQuery: string;
+}
+
+function getRepositoryUrl(workspace: string, slug: string) {
+ return `https://bitbucket.org/${workspace}/${slug}`;
+}
+
+export default function BitbucketCloudSearchForm(props: BitbucketCloudSearchFormProps) {
+ const { isLastPage, loadingMore, repositories = [], searching, searchQuery } = props;
+
+ if (repositories.length === 0 && searchQuery.length === 0 && !searching) {
+ return (
+ <Alert className="spacer-top" variant="warning">
+ <FormattedMessage
+ defaultMessage={translate('onboarding.create_project.bitbucketcloud.no_projects')}
+ id="onboarding.create_project.bitbucketcloud.no_projects"
+ values={{
+ link: (
+ <Link
+ to={{
+ pathname: '/projects/create',
+ query: { mode: CreateProjectModes.BitbucketCloud, resetPat: 1 }
+ }}>
+ {translate('onboarding.create_project.update_your_token')}
+ </Link>
+ )
+ }}
+ />
+ </Alert>
+ );
+ }
+
+ return (
+ <div className="boxed-group big-padded create-project-import">
+ <SearchBox
+ className="spacer"
+ loading={searching}
+ minLength={3}
+ onChange={props.onSearch}
+ placeholder={translate('onboarding.create_project.search_prompt')}
+ />
+
+ <hr />
+
+ {repositories.length === 0 ? (
+ <div className="padded">{translate('no_results')}</div>
+ ) : (
+ <table className="data zebra zebra-hover">
+ <tbody>
+ {repositories.map(repository => (
+ <tr key={repository.uuid}>
+ <td>
+ <Tooltip overlay={repository.slug}>
+ <strong className="project-name display-inline-block text-ellipsis">
+ {repository.sqProjectKey ? (
+ <Link to={getProjectUrl(repository.sqProjectKey)}>
+ <QualifierIcon
+ className="spacer-right"
+ qualifier={ComponentQualifier.Project}
+ />
+ {repository.sqProjectKey}
+ </Link>
+ ) : (
+ repository.name
+ )}
+ </strong>
+ </Tooltip>
+ <br />
+ <Tooltip overlay={repository.slug}>
+ <span className="text-muted project-path display-inline-block text-ellipsis">
+ {repository.slug}
+ </span>
+ </Tooltip>
+ </td>
+ <td>
+ <a
+ className="display-inline-flex-center big-spacer-right"
+ href={getRepositoryUrl(repository.workspace, repository.slug)}
+ rel="noopener noreferrer"
+ target="_blank">
+ <DetachIcon className="little-spacer-right" />
+ {translate('onboarding.create_project.bitbucketcloud.link')}
+ </a>
+ </td>
+ {repository.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 className="text-right">
+ <Button
+ onClick={() => {
+ /* Todo for import repo */
+ }}>
+ {translate('onboarding.create_project.set_up')}
+ {false && <DeferredSpinner className="spacer-left" />}
+ </Button>
+ </td>
+ )}
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ )}
+ <footer className="spacer-top note text-center">
+ {translateWithParameters(
+ 'x_of_y_shown',
+ formatMeasure(repositories.length, 'INT', null),
+ isLastPage ? formatMeasure(repositories.length, 'INT', null) : translate('unknown')
+ )}
+ {!isLastPage && (
+ <Button
+ className="spacer-left"
+ disabled={loadingMore}
+ data-test="show-more"
+ onClick={props.onLoadMore}>
+ {translate('show_more')}
+ </Button>
+ )}
+ {loadingMore && <DeferredSpinner className="text-bottom spacer-left position-absolute" />}
+ </footer>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx
index d87c95f825c..7ab9a59b924 100644
--- a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx
@@ -80,13 +80,13 @@ export default function GitlabProjectSelectionForm(props: GitlabProjectSelection
}
return (
- <div className="boxed-group big-padded create-project-import-gitlab">
+ <div className="boxed-group big-padded create-project-import">
<SearchBox
className="spacer"
loading={searching}
minLength={3}
onChange={props.onSearch}
- placeholder={translate('onboarding.create_project.gitlab.search_prompt')}
+ placeholder={translate('onboarding.create_project.search_prompt')}
/>
<hr />
@@ -143,7 +143,7 @@ export default function GitlabProjectSelectionForm(props: GitlabProjectSelection
<Button
disabled={!!importingGitlabProjectId}
onClick={() => props.onImport(project.id)}>
- {translate('onboarding.create_project.gitlab.set_up')}
+ {translate('onboarding.create_project.set_up')}
{importingGitlabProjectId === project.id && (
<DeferredSpinner className="spacer-left" />
)}
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreate-test.tsx
index 031e730d056..ffdec037bb5 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreate-test.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreate-test.tsx
@@ -20,26 +20,34 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
-import { checkPersonalAccessTokenIsValid } from '../../../../api/alm-integrations';
+import { searchForBitbucketCloudRepositories } from '../../../../api/alm-integrations';
+import {
+ mockBitbucketCloudRepository,
+ mockBitbucketRepository
+} from '../../../../helpers/mocks/alm-integrations';
import { mockBitbucketCloudAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
-import BitbucketCloudProjectCreate from '../BitbucketCloudProjectCreate';
+import BitbucketCloudProjectCreate, {
+ BITBUCKET_PROJECTS_PAGESIZE
+} from '../BitbucketCloudProjectCreate';
jest.mock('../../../../api/alm-integrations', () => {
return {
+ searchForBitbucketCloudRepositories: jest
+ .fn()
+ .mockResolvedValue({ isLastPage: true, repositories: [] }),
checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue({ status: true }),
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null)
};
});
it('Should render correctly', async () => {
- let wrapper = shallowRender();
+ const wrapper = shallowRender({ settings: [] });
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
- (checkPersonalAccessTokenIsValid as jest.Mock).mockRejectedValueOnce({});
- wrapper = shallowRender();
+ wrapper.setProps({ settings: [mockBitbucketCloudAlmSettingsInstance()] });
await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot('Need App password');
+ expect(wrapper).toMatchSnapshot('Setting changeds');
});
it('Should handle app password correctly', async () => {
@@ -50,6 +58,72 @@ it('Should handle app password correctly', async () => {
expect(wrapper.state().showPersonalAccessTokenForm).toBe(false);
});
+it('Should handle error correctly', async () => {
+ (searchForBitbucketCloudRepositories as jest.Mock).mockRejectedValueOnce({});
+
+ const wrapper = shallowRender();
+ wrapper.setState({
+ showPersonalAccessTokenForm: false,
+ repositories: [mockBitbucketCloudRepository()],
+ projectsPaging: { pageIndex: 2, pageSize: BITBUCKET_PROJECTS_PAGESIZE }
+ });
+ await wrapper.instance().handlePersonalAccessTokenCreated();
+ expect(wrapper.state().repositories).toHaveLength(0);
+ expect(wrapper.state().projectsPaging.pageIndex).toBe(1);
+ expect(wrapper.state().showPersonalAccessTokenForm).toBe(true);
+ expect(wrapper.state().resetPat).toBe(true);
+});
+
+it('Should load repository', async () => {
+ (searchForBitbucketCloudRepositories as jest.Mock).mockResolvedValueOnce({
+ isLastPage: true,
+ repositories: [mockBitbucketRepository(), mockBitbucketRepository({ sqProjectKey: 'sq-key' })]
+ });
+
+ const wrapper = shallowRender();
+ await wrapper.instance().handlePersonalAccessTokenCreated();
+ expect(wrapper.state().repositories).toHaveLength(2);
+});
+
+it('Should load more repository', async () => {
+ (searchForBitbucketCloudRepositories as jest.Mock).mockResolvedValueOnce({
+ isLastPage: true,
+ repositories: [mockBitbucketRepository(), mockBitbucketRepository({ sqProjectKey: 'sq-key' })]
+ });
+
+ const wrapper = shallowRender();
+ wrapper.setState({ showPersonalAccessTokenForm: false, isLastPage: false });
+ wrapper.instance().handleLoadMore();
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().repositories).toHaveLength(2);
+ expect(wrapper.state().projectsPaging.pageIndex).toBe(2);
+});
+
+it('Should handle search repository', async () => {
+ (searchForBitbucketCloudRepositories as jest.Mock).mockResolvedValueOnce({
+ isLastPage: true,
+ repositories: [mockBitbucketRepository(), mockBitbucketRepository({ sqProjectKey: 'sq-key' })]
+ });
+
+ const wrapper = shallowRender();
+ wrapper.setState({
+ isLastPage: false,
+ showPersonalAccessTokenForm: false,
+ projectsPaging: { pageIndex: 2, pageSize: BITBUCKET_PROJECTS_PAGESIZE },
+ repositories: [mockBitbucketCloudRepository()]
+ });
+ wrapper.instance().handleSearch('test');
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().repositories).toHaveLength(2);
+ expect(wrapper.state().projectsPaging.pageIndex).toBe(1);
+ expect(searchForBitbucketCloudRepositories).toHaveBeenLastCalledWith(
+ 'key',
+ 'test',
+ BITBUCKET_PROJECTS_PAGESIZE,
+ 1
+ );
+});
+
function shallowRender(props?: Partial<BitbucketCloudProjectCreate['props']>) {
return shallow<BitbucketCloudProjectCreate>(
<BitbucketCloudProjectCreate
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreateRender-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreateRender-test.tsx
index 228467a76d6..3b07789e6e5 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreateRender-test.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreateRender-test.tsx
@@ -38,10 +38,16 @@ it('Should render correctly', () => {
function shallowRender(props?: Partial<BitbucketCloudProjectCreateRendererProps>) {
return shallow(
<BitbucketCloudProjectCreateRenderer
- onPersonalAccessTokenCreated={jest.fn()}
+ isLastPage={true}
loading={false}
- settings={mockBitbucketCloudAlmSettingsInstance()}
+ loadingMore={false}
+ onLoadMore={jest.fn()}
+ onPersonalAccessTokenCreated={jest.fn()}
+ onSearch={jest.fn()}
resetPat={false}
+ searching={false}
+ searchQuery={''}
+ settings={mockBitbucketCloudAlmSettingsInstance()}
showPersonalAccessTokenForm={false}
{...props}
/>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudSearchForm-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudSearchForm-test.tsx
new file mode 100644
index 00000000000..440b7e2ebf7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudSearchForm-test.tsx
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { mockBitbucketCloudRepository } from '../../../../helpers/mocks/alm-integrations';
+import BitbucketCloudSearchForm, {
+ BitbucketCloudSearchFormProps
+} from '../BitbucketCloudSearchForm';
+
+it('Should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+ expect(
+ shallowRender({
+ repositories: [
+ mockBitbucketCloudRepository(),
+ mockBitbucketCloudRepository({ sqProjectKey: 'sq-key' })
+ ],
+ isLastPage: false
+ })
+ ).toMatchSnapshot('Show more');
+ expect(
+ shallowRender({
+ repositories: [mockBitbucketCloudRepository()],
+ isLastPage: true
+ })
+ ).toMatchSnapshot('Show no more');
+ expect(
+ shallowRender({
+ repositories: [mockBitbucketCloudRepository()],
+ isLastPage: false,
+ loadingMore: true
+ })
+ ).toMatchSnapshot('Loading more');
+ expect(
+ shallowRender({
+ repositories: [],
+ isLastPage: false,
+ searching: true
+ })
+ ).toMatchSnapshot('Searching');
+});
+
+function shallowRender(props?: Partial<BitbucketCloudSearchFormProps>) {
+ return shallow(
+ <BitbucketCloudSearchForm
+ isLastPage={true}
+ loadingMore={false}
+ onLoadMore={jest.fn()}
+ onSearch={jest.fn()}
+ searchQuery={''}
+ searching={false}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreate-test.tsx.snap
index 77cd3e633d1..077424a7a23 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreate-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreate-test.tsx.snap
@@ -3,25 +3,33 @@
exports[`Should render correctly 1`] = `
<BitbucketCloudProjectCreateRenderer
canAdmin={true}
+ isLastPage={true}
loading={false}
+ loadingMore={false}
+ onLoadMore={[Function]}
onPersonalAccessTokenCreated={[Function]}
+ onSearch={[Function]}
+ repositories={Array []}
resetPat={false}
- settings={
- Object {
- "alm": "bitbucketcloud",
- "key": "key",
- }
- }
+ searchQuery=""
+ searching={false}
showPersonalAccessTokenForm={true}
/>
`;
-exports[`Should render correctly: Need App password 1`] = `
+exports[`Should render correctly: Setting changeds 1`] = `
<BitbucketCloudProjectCreateRenderer
canAdmin={true}
+ isLastPage={true}
loading={false}
+ loadingMore={false}
+ onLoadMore={[Function]}
onPersonalAccessTokenCreated={[Function]}
+ onSearch={[Function]}
+ repositories={Array []}
resetPat={false}
+ searchQuery=""
+ searching={false}
settings={
Object {
"alm": "bitbucketcloud",
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreateRender-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreateRender-test.tsx.snap
index aac4d670be2..0cdda04f673 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreateRender-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreateRender-test.tsx.snap
@@ -17,9 +17,14 @@ exports[`Should render correctly 1`] = `
</span>
}
/>
- <p>
- Placeholder for next step
- </p>
+ <BitbucketCloudSearchForm
+ isLastPage={true}
+ loadingMore={false}
+ onLoadMore={[MockFunction]}
+ onSearch={[MockFunction]}
+ searchQuery=""
+ searching={false}
+ />
</Fragment>
`;
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudSearchForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudSearchForm-test.tsx.snap
new file mode 100644
index 00000000000..6c8f6d8e56f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudSearchForm-test.tsx.snap
@@ -0,0 +1,370 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Should render correctly 1`] = `
+<Alert
+ className="spacer-top"
+ variant="warning"
+>
+ <FormattedMessage
+ defaultMessage="onboarding.create_project.bitbucketcloud.no_projects"
+ id="onboarding.create_project.bitbucketcloud.no_projects"
+ values={
+ Object {
+ "link": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/projects/create",
+ "query": Object {
+ "mode": "bitbucketcloud",
+ "resetPat": 1,
+ },
+ }
+ }
+ >
+ onboarding.create_project.update_your_token
+ </Link>,
+ }
+ }
+ />
+</Alert>
+`;
+
+exports[`Should render correctly: Loading more 1`] = `
+<div
+ className="boxed-group big-padded create-project-import"
+>
+ <SearchBox
+ className="spacer"
+ loading={false}
+ minLength={3}
+ onChange={[MockFunction]}
+ placeholder="onboarding.create_project.search_prompt"
+ />
+ <hr />
+ <table
+ className="data zebra zebra-hover"
+ >
+ <tbody>
+ <tr
+ key="1"
+ >
+ <td>
+ <Tooltip
+ overlay="project__repo"
+ >
+ <strong
+ className="project-name display-inline-block text-ellipsis"
+ >
+ Repo
+ </strong>
+ </Tooltip>
+ <br />
+ <Tooltip
+ overlay="project__repo"
+ >
+ <span
+ className="text-muted project-path display-inline-block text-ellipsis"
+ >
+ project__repo
+ </span>
+ </Tooltip>
+ </td>
+ <td>
+ <a
+ className="display-inline-flex-center big-spacer-right"
+ href="https://bitbucket.org/undefined/project__repo"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ <DetachIcon
+ className="little-spacer-right"
+ />
+ onboarding.create_project.bitbucketcloud.link
+ </a>
+ </td>
+ <td
+ className="text-right"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ onboarding.create_project.set_up
+ </Button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <footer
+ className="spacer-top note text-center"
+ >
+ x_of_y_shown.1.unknown
+ <Button
+ className="spacer-left"
+ data-test="show-more"
+ disabled={true}
+ onClick={[MockFunction]}
+ >
+ show_more
+ </Button>
+ <DeferredSpinner
+ className="text-bottom spacer-left position-absolute"
+ />
+ </footer>
+</div>
+`;
+
+exports[`Should render correctly: Searching 1`] = `
+<div
+ className="boxed-group big-padded create-project-import"
+>
+ <SearchBox
+ className="spacer"
+ loading={true}
+ minLength={3}
+ onChange={[MockFunction]}
+ placeholder="onboarding.create_project.search_prompt"
+ />
+ <hr />
+ <div
+ className="padded"
+ >
+ no_results
+ </div>
+ <footer
+ className="spacer-top note text-center"
+ >
+ x_of_y_shown.0.unknown
+ <Button
+ className="spacer-left"
+ data-test="show-more"
+ disabled={false}
+ onClick={[MockFunction]}
+ >
+ show_more
+ </Button>
+ </footer>
+</div>
+`;
+
+exports[`Should render correctly: Show more 1`] = `
+<div
+ className="boxed-group big-padded create-project-import"
+>
+ <SearchBox
+ className="spacer"
+ loading={false}
+ minLength={3}
+ onChange={[MockFunction]}
+ placeholder="onboarding.create_project.search_prompt"
+ />
+ <hr />
+ <table
+ className="data zebra zebra-hover"
+ >
+ <tbody>
+ <tr
+ key="1"
+ >
+ <td>
+ <Tooltip
+ overlay="project__repo"
+ >
+ <strong
+ className="project-name display-inline-block text-ellipsis"
+ >
+ Repo
+ </strong>
+ </Tooltip>
+ <br />
+ <Tooltip
+ overlay="project__repo"
+ >
+ <span
+ className="text-muted project-path display-inline-block text-ellipsis"
+ >
+ project__repo
+ </span>
+ </Tooltip>
+ </td>
+ <td>
+ <a
+ className="display-inline-flex-center big-spacer-right"
+ href="https://bitbucket.org/undefined/project__repo"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ <DetachIcon
+ className="little-spacer-right"
+ />
+ onboarding.create_project.bitbucketcloud.link
+ </a>
+ </td>
+ <td
+ className="text-right"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ onboarding.create_project.set_up
+ </Button>
+ </td>
+ </tr>
+ <tr
+ key="1"
+ >
+ <td>
+ <Tooltip
+ overlay="project__repo"
+ >
+ <strong
+ className="project-name display-inline-block text-ellipsis"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": undefined,
+ "id": "sq-key",
+ },
+ }
+ }
+ >
+ <QualifierIcon
+ className="spacer-right"
+ qualifier="TRK"
+ />
+ sq-key
+ </Link>
+ </strong>
+ </Tooltip>
+ <br />
+ <Tooltip
+ overlay="project__repo"
+ >
+ <span
+ className="text-muted project-path display-inline-block text-ellipsis"
+ >
+ project__repo
+ </span>
+ </Tooltip>
+ </td>
+ <td>
+ <a
+ className="display-inline-flex-center big-spacer-right"
+ href="https://bitbucket.org/undefined/project__repo"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ <DetachIcon
+ className="little-spacer-right"
+ />
+ onboarding.create_project.bitbucketcloud.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>
+ </tr>
+ </tbody>
+ </table>
+ <footer
+ className="spacer-top note text-center"
+ >
+ x_of_y_shown.2.unknown
+ <Button
+ className="spacer-left"
+ data-test="show-more"
+ disabled={false}
+ onClick={[MockFunction]}
+ >
+ show_more
+ </Button>
+ </footer>
+</div>
+`;
+
+exports[`Should render correctly: Show no more 1`] = `
+<div
+ className="boxed-group big-padded create-project-import"
+>
+ <SearchBox
+ className="spacer"
+ loading={false}
+ minLength={3}
+ onChange={[MockFunction]}
+ placeholder="onboarding.create_project.search_prompt"
+ />
+ <hr />
+ <table
+ className="data zebra zebra-hover"
+ >
+ <tbody>
+ <tr
+ key="1"
+ >
+ <td>
+ <Tooltip
+ overlay="project__repo"
+ >
+ <strong
+ className="project-name display-inline-block text-ellipsis"
+ >
+ Repo
+ </strong>
+ </Tooltip>
+ <br />
+ <Tooltip
+ overlay="project__repo"
+ >
+ <span
+ className="text-muted project-path display-inline-block text-ellipsis"
+ >
+ project__repo
+ </span>
+ </Tooltip>
+ </td>
+ <td>
+ <a
+ className="display-inline-flex-center big-spacer-right"
+ href="https://bitbucket.org/undefined/project__repo"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ <DetachIcon
+ className="little-spacer-right"
+ />
+ onboarding.create_project.bitbucketcloud.link
+ </a>
+ </td>
+ <td
+ className="text-right"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ onboarding.create_project.set_up
+ </Button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <footer
+ className="spacer-top note text-center"
+ >
+ x_of_y_shown.1.1
+ </footer>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap
index b416707e214..ae69acae3ee 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap
@@ -2,14 +2,14 @@
exports[`should render correctly: importing 1`] = `
<div
- className="boxed-group big-padded create-project-import-gitlab"
+ className="boxed-group big-padded create-project-import"
>
<SearchBox
className="spacer"
loading={false}
minLength={3}
onChange={[MockFunction]}
- placeholder="onboarding.create_project.gitlab.search_prompt"
+ placeholder="onboarding.create_project.search_prompt"
/>
<hr />
<table
@@ -60,7 +60,7 @@ exports[`should render correctly: importing 1`] = `
disabled={true}
onClick={[Function]}
>
- onboarding.create_project.gitlab.set_up
+ onboarding.create_project.set_up
</Button>
</td>
</tr>
@@ -175,14 +175,14 @@ exports[`should render correctly: no projects 1`] = `
exports[`should render correctly: no projects when searching 1`] = `
<div
- className="boxed-group big-padded create-project-import-gitlab"
+ className="boxed-group big-padded create-project-import"
>
<SearchBox
className="spacer"
loading={false}
minLength={3}
onChange={[MockFunction]}
- placeholder="onboarding.create_project.gitlab.search_prompt"
+ placeholder="onboarding.create_project.search_prompt"
/>
<hr />
<div
@@ -201,14 +201,14 @@ exports[`should render correctly: no projects when searching 1`] = `
exports[`should render correctly: projects 1`] = `
<div
- className="boxed-group big-padded create-project-import-gitlab"
+ className="boxed-group big-padded create-project-import"
>
<SearchBox
className="spacer"
loading={false}
minLength={3}
onChange={[MockFunction]}
- placeholder="onboarding.create_project.gitlab.search_prompt"
+ placeholder="onboarding.create_project.search_prompt"
/>
<hr />
<table
@@ -259,7 +259,7 @@ exports[`should render correctly: projects 1`] = `
disabled={false}
onClick={[Function]}
>
- onboarding.create_project.gitlab.set_up
+ onboarding.create_project.set_up
</Button>
</td>
</tr>
diff --git a/server/sonar-web/src/main/js/apps/create/project/style.css b/server/sonar-web/src/main/js/apps/create/project/style.css
index e8c38062d88..54d5d4220fa 100644
--- a/server/sonar-web/src/main/js/apps/create/project/style.css
+++ b/server/sonar-web/src/main/js/apps/create/project/style.css
@@ -68,19 +68,19 @@
color: var(--green);
}
-.create-project-import-gitlab table > tbody > tr > td {
+.create-project-import table > tbody > tr > td {
vertical-align: middle;
}
-.create-project-import-gitlab .project-name,
-.create-project-import-gitlab .project-path {
+.create-project-import .project-name,
+.create-project-import .project-path {
max-width: 400px;
}
-.create-project-import-gitlab .sq-project-link {
+.create-project-import .sq-project-link {
max-width: 300px;
}
-.create-project-import-gitlab .already-set-up svg {
+.create-project-import .already-set-up svg {
color: var(--green);
}