diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2023-08-10 11:28:45 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-08-14 20:02:57 +0000 |
commit | ee27a13ee434d73973a16ae313d2d1447046d22a (patch) | |
tree | 00f56f16454d8e7280869005e19b008d04da7edb /server/sonar-web/src/main/js/apps/create/project | |
parent | 1e244b724919050aa4c7d8d371418400db628c58 (diff) | |
download | sonarqube-ee27a13ee434d73973a16ae313d2d1447046d22a.tar.gz sonarqube-ee27a13ee434d73973a16ae313d2d1447046d22a.zip |
SONAR-20086 Migrate Bitbucket Cloud import page to the new UI
Diffstat (limited to 'server/sonar-web/src/main/js/apps/create/project')
6 files changed, 84 insertions, 135 deletions
diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx index 0a4a2baf20c..8012134258b 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx @@ -26,6 +26,7 @@ import { Location, Router } from '../../../../components/hoc/withRouter'; import { BitbucketCloudRepository } from '../../../../types/alm-integration'; import { AlmSettingsInstance } from '../../../../types/alm-settings'; import { Paging } from '../../../../types/types'; +import { BITBUCKET_CLOUD_PROJECTS_PAGESIZE } from '../constants'; import { CreateProjectApiCallback } from '../types'; import BitbucketCloudProjectCreateRenderer from './BitbucketCloudProjectCreateRender'; @@ -51,7 +52,6 @@ interface State { showPersonalAccessTokenForm: boolean; } -export const BITBUCKET_CLOUD_PROJECTS_PAGESIZE = 30; export default class BitbucketCloudProjectCreate extends React.PureComponent<Props, State> { mounted = false; diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx index 770e8cac2ec..992f8046e4f 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx @@ -17,13 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { DeferredSpinner, LightPrimary, Title } from 'design-system'; import * as React from 'react'; import { translate } from '../../../../helpers/l10n'; -import { getBaseUrl } from '../../../../helpers/system'; import { BitbucketCloudRepository } from '../../../../types/alm-integration'; import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown'; -import CreateProjectPageHeader from '../components/CreateProjectPageHeader'; import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm'; import WrongBindingCountAlert from '../components/WrongBindingCountAlert'; import BitbucketCloudSearchForm from './BitbucketCloudSearchForm'; @@ -66,19 +65,14 @@ export default function BitbucketCloudProjectCreateRenderer( return ( <> - <CreateProjectPageHeader - title={ - <span className="text-middle"> - <img - alt="" // Should be ignored by screen readers - className="spacer-right" - height="24" - src={`${getBaseUrl()}/images/alm/bitbucket.svg`} - /> - {translate('onboarding.create_project.bitbucketcloud.title')} - </span> - } - /> + <header className="sw-mb-10"> + <Title className="sw-mb-4"> + {translate('onboarding.create_project.bitbucketcloud.title')} + </Title> + <LightPrimary className="sw-body-sm"> + {translate('onboarding.create_project.bitbucketcloud.subtitle')} + </LightPrimary> + </header> <AlmSettingsInstanceDropdown almKey={AlmKeys.BitbucketCloud} @@ -87,7 +81,7 @@ export default function BitbucketCloudProjectCreateRenderer( onChangeConfig={props.onSelectedAlmInstanceChange} /> - {loading && <i className="spinner" />} + <DeferredSpinner loading={loading} /> {!loading && !selectedAlmInstance && ( <WrongBindingCountAlert alm={AlmKeys.BitbucketCloud} canAdmin={!!canAdmin} /> diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx index 8b90691f348..206a2bcb082 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx @@ -17,22 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { FlagMessage, InputSearch, LightPrimary, Link } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../../components/common/Link'; -import SearchBox from '../../../../components/controls/SearchBox'; -import Tooltip from '../../../../components/controls/Tooltip'; -import { Button } from '../../../../components/controls/buttons'; -import CheckIcon from '../../../../components/icons/CheckIcon'; -import QualifierIcon from '../../../../components/icons/QualifierIcon'; -import { Alert } from '../../../../components/ui/Alert'; -import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; -import { translate, translateWithParameters } from '../../../../helpers/l10n'; -import { formatMeasure } from '../../../../helpers/measures'; -import { getProjectUrl, queryToSearch } from '../../../../helpers/urls'; +import ListFooter from '../../../../components/controls/ListFooter'; +import { translate } from '../../../../helpers/l10n'; +import { getBaseUrl } from '../../../../helpers/system'; +import { queryToSearch } from '../../../../helpers/urls'; import { BitbucketCloudRepository } from '../../../../types/alm-integration'; -import { ComponentQualifier } from '../../../../types/component'; -import { MetricType } from '../../../../types/metrics'; +import AlmRepoItem from '../components/AlmRepoItem'; +import { BITBUCKET_CLOUD_PROJECTS_PAGESIZE } from '../constants'; import { CreateProjectModes } from '../types'; export interface BitbucketCloudSearchFormProps { @@ -55,7 +49,7 @@ export default function BitbucketCloudSearchForm(props: BitbucketCloudSearchForm if (repositories.length === 0 && searchQuery.length === 0 && !searching) { return ( - <Alert className="spacer-top" variant="warning"> + <FlagMessage className="sw-mt-2" variant="warning"> <FormattedMessage defaultMessage={translate('onboarding.create_project.bitbucketcloud.no_projects')} id="onboarding.create_project.bitbucketcloud.no_projects" @@ -72,106 +66,56 @@ export default function BitbucketCloudSearchForm(props: BitbucketCloudSearchForm ), }} /> - </Alert> + </FlagMessage> ); } 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 /> + <div> + <div className="sw-flex sw-items-center sw-mb-6 sw-w-abs-400"> + <InputSearch + clearIconAriaLabel={translate('clear')} + loading={searching} + minLength={3} + onChange={props.onSearch} + placeholder={translate('onboarding.create_project.search_prompt')} + size="full" + value={searchQuery} + /> + </div> {repositories.length === 0 ? ( - <div className="padded">{translate('no_results')}</div> + <div className="sw-py-6 sw-px-2"> + <LightPrimary className="sw-body-sm">{translate('no_results')}</LightPrimary> + </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.name} - </Link> - ) : ( - repository.name - )} - </strong> - </Tooltip> - <br /> - <Tooltip overlay={repository.projectKey}> - <span className="text-muted project-path display-inline-block text-ellipsis"> - {repository.projectKey} - </span> - </Tooltip> - </td> - <td> - <Link - className="display-inline-flex-center big-spacer-right" - to={getRepositoryUrl(repository.workspace, repository.slug)} - target="_blank" - > - {translate('onboarding.create_project.bitbucketcloud.link')} - </Link> - </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={() => { - props.onImport(repository.slug); - }} - > - {translate('onboarding.create_project.set_up')} - </Button> - </td> - )} - </tr> - ))} - </tbody> - </table> + <div className="sw-flex sw-flex-col sw-gap-3"> + {repositories.map((r) => ( + <AlmRepoItem + key={r.uuid} + almKey={r.slug} + almUrl={getRepositoryUrl(r.workspace, r.slug)} + almUrlText={translate('onboarding.create_project.bitbucketcloud.link')} + almIconSrc={`${getBaseUrl()}/images/alm/bitbucket.svg`} + sqProjectKey={r.sqProjectKey} + onImport={props.onImport} + primaryTextNode={<span title={r.name}>{r.name}</span>} + secondaryTextNode={<span title={r.projectKey}>{r.projectKey}</span>} + /> + ))} + </div> )} - <footer className="spacer-top note text-center"> - {isLastPage && - translateWithParameters( - 'x_of_y_shown', - formatMeasure(repositories.length, MetricType.Integer, null), - formatMeasure(repositories.length, MetricType.Integer, null) - )} - {!isLastPage && ( - <Button - className="spacer-left" - disabled={loadingMore} - data-test="show-more" - onClick={props.onLoadMore} - > - {translate('show_more')} - </Button> - )} - <DeferredSpinner - className="text-bottom spacer-left position-absolute" - loading={loadingMore} - /> - </footer> + + <ListFooter + className="sw-mb-10" + count={repositories.length} + // we don't know the total, so only provide when we've reached the last page + total={isLastPage ? repositories.length : undefined} + pageSize={BITBUCKET_CLOUD_PROJECTS_PAGESIZE} + loadMore={props.onLoadMore} + loading={loadingMore} + useMIUIButtons + /> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx index 17a29443b71..69de3118fd3 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx @@ -76,12 +76,12 @@ function renderRepositoryList(props: GitHubProjectCreateRendererProps) { <div className="sw-flex sw-items-center sw-mb-6"> <InputSearch size="large" + loading={loadingRepositories} onChange={props.onSearch} placeholder={translate('onboarding.create_project.search_repositories')} value={searchQuery} clearIconAriaLabel={translate('clear')} /> - <DeferredSpinner loading={loadingRepositories} className="sw-ml-2" /> </div> {repositories.length === 0 ? ( diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx index 37558ec5f18..af4d30aa07c 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { act, screen, within } from '@testing-library/react'; +import { act, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; @@ -29,6 +29,7 @@ import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServi import { renderApp } from '../../../../helpers/testReactTestingUtils'; import { byLabelText, byRole, byText } from '../../../../helpers/testSelector'; import CreateProjectPage from '../CreateProjectPage'; +import { BITBUCKET_CLOUD_PROJECTS_PAGESIZE } from '../constants'; jest.mock('../../../../api/alm-integrations'); jest.mock('../../../../api/alm-settings'); @@ -148,7 +149,7 @@ it('should show import project feature when PAT is already set', async () => { projectItem = screen.getByRole('row', { name: /BitbucketCloud Repo 2/ }); const setupButton = within(projectItem).getByRole('button', { - name: 'onboarding.create_project.set_up', + name: 'onboarding.create_project.import', }); await user.click(setupButton); @@ -181,7 +182,7 @@ it('should show search filter when PAT is already set', async () => { expect(searchForBitbucketCloudRepositories).toHaveBeenLastCalledWith( 'conf-bitbucketcloud-2', '', - 30, + BITBUCKET_CLOUD_PROJECTS_PAGESIZE, 1 ); @@ -194,7 +195,7 @@ it('should show search filter when PAT is already set', async () => { expect(searchForBitbucketCloudRepositories).toHaveBeenLastCalledWith( 'conf-bitbucketcloud-2', 'search', - 30, + BITBUCKET_CLOUD_PROJECTS_PAGESIZE, 1 ); }); @@ -217,7 +218,10 @@ it('should show no result message when there are no projects', async () => { it('should have load more', async () => { const user = userEvent.setup(); - almIntegrationHandler.createRandomBitbucketCloudProjectsWithLoadMore(2, 4); + almIntegrationHandler.createRandomBitbucketCloudProjectsWithLoadMore( + BITBUCKET_CLOUD_PROJECTS_PAGESIZE, + BITBUCKET_CLOUD_PROJECTS_PAGESIZE + 1 + ); renderCreateProject(); expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument(); @@ -227,23 +231,28 @@ it('should have load more', async () => { await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]); }); - const loadMore = screen.getByRole('button', { name: 'show_more' }); - expect(loadMore).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'show_more' })).toBeInTheDocument(); /* * Next api call response will simulate reaching the last page so we can test the * loadmore button disapperance. */ - almIntegrationHandler.createRandomBitbucketCloudProjectsWithLoadMore(4, 4); - await user.click(loadMore); + almIntegrationHandler.createRandomBitbucketCloudProjectsWithLoadMore( + BITBUCKET_CLOUD_PROJECTS_PAGESIZE + 1, + BITBUCKET_CLOUD_PROJECTS_PAGESIZE + 1 + ); + await user.click(screen.getByRole('button', { name: 'show_more' })); expect(searchForBitbucketCloudRepositories).toHaveBeenLastCalledWith( 'conf-bitbucketcloud-2', '', - 30, + BITBUCKET_CLOUD_PROJECTS_PAGESIZE, 2 ); - expect(loadMore).not.toBeInTheDocument(); + + await waitFor(() => { + expect(screen.queryByRole('button', { name: 'show_more' })).not.toBeInTheDocument(); + }); }); function renderCreateProject() { diff --git a/server/sonar-web/src/main/js/apps/create/project/constants.ts b/server/sonar-web/src/main/js/apps/create/project/constants.ts index 209b26dca3f..3a00eafc4a4 100644 --- a/server/sonar-web/src/main/js/apps/create/project/constants.ts +++ b/server/sonar-web/src/main/js/apps/create/project/constants.ts @@ -20,3 +20,5 @@ export const PROJECT_NAME_MAX_LEN = 255; export const DEFAULT_BBS_PAGE_SIZE = 25; + +export const BITBUCKET_CLOUD_PROJECTS_PAGESIZE = 20; |