diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2021-05-19 10:50:19 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-05-21 20:03:37 +0000 |
commit | 1a84279cce2f1ea0d83cbd7eb4f24088533fe619 (patch) | |
tree | 533aa39193b66c68bd7597ffc177b379c7a16331 /server/sonar-web/src/main/js/apps/create/project | |
parent | bdbcff0bfa506c03d4952fda567fba8147ac01c6 (diff) | |
download | sonarqube-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')
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); } |