* 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, 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 { Alert } from '../../../../components/ui/Alert';
import { translate } from '../../../../helpers/l10n';
import { queryToSearch } from '../../../../helpers/urls';
import {
export interface BitbucketImportRepositoryFormProps {
onSearch: (query: string) => void;
- onSelectRepository: (repo: BitbucketRepository) => void;
+ onImportRepository: (repo: BitbucketRepository) => void;
projects?: BitbucketProject[];
projectRepositories?: BitbucketProjectRepositories;
searching: boolean;
searchResults?: BitbucketRepository[];
- selectedRepository?: BitbucketRepository;
}
export default function BitbucketImportRepositoryForm(props: BitbucketImportRepositoryFormProps) {
- const {
- projects = [],
- projectRepositories = {},
- searchResults,
- searching,
- selectedRepository,
- } = props;
+ const { projects = [], projectRepositories = {}, searchResults, searching } = props;
if (projects.length === 0) {
return (
- <Alert className="spacer-top" variant="warning">
+ <FlagMessage variant="warning">
<FormattedMessage
defaultMessage={translate('onboarding.create_project.no_bbs_projects')}
id="onboarding.create_project.no_bbs_projects"
),
}}
/>
- </Alert>
+ </FlagMessage>
);
}
return (
- <div className="create-project-import-bbs">
- <SearchBox
- onChange={props.onSearch}
- placeholder={translate('onboarding.create_project.search_repositories_by_name')}
- />
+ <div>
+ <div className="sw-mb-10 sw-w-abs-400">
+ <InputSearch
+ searchInputAriaLabel={translate('onboarding.create_project.search_repositories_by_name')}
+ clearIconAriaLabel={translate('clear')}
+ onChange={props.onSearch}
+ placeholder={translate('onboarding.create_project.search_repositories_by_name')}
+ size="full"
+ />
+ </div>
{searching || searchResults ? (
<BitbucketSearchResults
- onSelectRepository={props.onSelectRepository}
+ onImportRepository={props.onImportRepository}
projects={projects}
searchResults={searchResults}
searching={searching}
- selectedRepository={selectedRepository}
/>
) : (
<BitbucketRepositories
- onSelectRepository={props.onSelectRepository}
+ onImportRepository={props.onImportRepository}
projectRepositories={projectRepositories}
projects={projects}
- selectedRepository={selectedRepository}
/>
)}
</div>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
+import { Accordion, FlagMessage, Link } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { colors } from '../../../../app/theme';
-import Link from '../../../../components/common/Link';
-import BoxedGroupAccordion from '../../../../components/controls/BoxedGroupAccordion';
-import Radio from '../../../../components/controls/Radio';
-import CheckIcon from '../../../../components/icons/CheckIcon';
-import { Alert } from '../../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
-import { getProjectUrl, queryToSearch } from '../../../../helpers/urls';
+import { getBaseUrl } from '../../../../helpers/system';
+import { queryToSearch } from '../../../../helpers/urls';
import { BitbucketProject, BitbucketRepository } from '../../../../types/alm-integration';
+import AlmRepoItem from '../components/AlmRepoItem';
import { CreateProjectModes } from '../types';
export interface BitbucketProjectAccordionProps {
onClick?: () => void;
- onSelectRepository: (repo: BitbucketRepository) => void;
+ onImportRepository: (repository: BitbucketRepository) => void;
open: boolean;
project?: BitbucketProject;
repositories: BitbucketRepository[];
- selectedRepository?: BitbucketRepository;
showingAllRepositories: boolean;
}
export default function BitbucketProjectAccordion(props: BitbucketProjectAccordionProps) {
- const { open, project, repositories, selectedRepository, showingAllRepositories } = props;
+ const { open, project, repositories, showingAllRepositories } = props;
const repositoryCount = repositories.length;
const title = project?.name ?? translate('search_results');
return (
- <BoxedGroupAccordion
- className={classNames('big-spacer-bottom', {
- open,
- 'not-clickable': !props.onClick,
- 'no-hover': !props.onClick,
- })}
+ <Accordion
+ className="sw-mb-6"
onClick={
props.onClick
? props.onClick
}
}
open={open}
- title={<h3>{title}</h3>}
+ header={title}
>
{open && (
<>
- <div className="display-flex-wrap">
+ <div className="sw-mb-4">
{repositoryCount === 0 && (
- <Alert variant="warning">
+ <FlagMessage variant="warning">
<FormattedMessage
defaultMessage={translate('onboarding.create_project.no_bbs_repos')}
id="onboarding.create_project.no_bbs_repos"
),
}}
/>
- </Alert>
+ </FlagMessage>
)}
- {repositories.map((repo) =>
- repo.sqProjectKey ? (
- <div
- className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
- key={repo.id}
- >
- <CheckIcon className="spacer-right" fill={colors.green} size={14} />
- <div className="overflow-hidden">
- <div className="little-spacer-bottom">
- <strong title={repo.name}>
- <Link to={getProjectUrl(repo.sqProjectKey)}>{repo.name}</Link>
- </strong>
- </div>
- <em>{translate('onboarding.create_project.repository_imported')}</em>
- </div>
- </div>
- ) : (
- <Radio
- checked={selectedRepository?.id === repo.id}
- className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden"
- key={repo.id}
- onCheck={() => props.onSelectRepository(repo)}
- value={String(repo.id)}
- >
- <strong title={repo.name}>{repo.name}</strong>
- </Radio>
- )
- )}
+ <div className="sw-flex sw-flex-col sw-gap-3">
+ {repositories.map((r) => (
+ <AlmRepoItem
+ key={r.name}
+ almKey={r.name}
+ almIconSrc={`${getBaseUrl()}/images/alm/bitbucket.svg`}
+ sqProjectKey={r.sqProjectKey}
+ onImport={() => props.onImportRepository(r)}
+ primaryTextNode={<span>{r.name}</span>}
+ />
+ ))}
+ </div>
</div>
{!showingAllRepositories && repositoryCount > 0 && (
- <Alert variant="warning">
+ <FlagMessage variant="warning">
{translateWithParameters(
'onboarding.create_project.only_showing_X_first_repos',
repositoryCount
)}
- </Alert>
+ </FlagMessage>
)}
</>
)}
- </BoxedGroupAccordion>
+ </Accordion>
);
}
projectRepositories?: BitbucketProjectRepositories;
searching: boolean;
searchResults?: BitbucketRepository[];
- selectedRepository?: BitbucketRepository;
showPersonalAccessTokenForm: boolean;
}
await this.fetchInitialData();
};
- handleImportRepository = () => {
- const { selectedAlmInstance, selectedRepository } = this.state;
+ handleImportRepository = (selectedRepository: BitbucketRepository) => {
+ const { selectedAlmInstance } = this.state;
- if (selectedAlmInstance && selectedRepository) {
+ if (selectedAlmInstance) {
this.props.onProjectSetupDone(
setupBitbucketServerProjectCreation({
almSetting: selectedAlmInstance.key,
}
if (!query) {
- this.setState({ searching: false, searchResults: undefined, selectedRepository: undefined });
+ this.setState({ searching: false, searchResults: undefined });
return;
}
- this.setState({ searching: true, selectedRepository: undefined });
+ this.setState({ searching: true });
searchForBitbucketServerRepositories(selectedAlmInstance.key, query)
.then(({ repositories }) => {
if (this.mounted) {
});
};
- handleSelectRepository = (selectedRepository: BitbucketRepository) => {
- this.setState({ selectedRepository });
- };
-
onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => {
this.setState({
selectedAlmInstance: instance,
projects,
searching,
searchResults,
- selectedRepository,
showPersonalAccessTokenForm,
} = this.state;
onImportRepository={this.handleImportRepository}
onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated}
onSearch={this.handleSearch}
- onSelectRepository={this.handleSelectRepository}
onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange}
projectRepositories={projectRepositories}
projects={projects}
resetPat={Boolean(location.query.resetPat)}
searchResults={searchResults}
searching={searching}
- selectedRepository={selectedRepository}
showPersonalAccessTokenForm={
showPersonalAccessTokenForm || Boolean(location.query.resetPat)
}
* 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, PageContentFontWrapper, Title } from 'design-system';
import * as React from 'react';
-import { Button } from '../../../../components/controls/buttons';
import { translate } from '../../../../helpers/l10n';
-import { getBaseUrl } from '../../../../helpers/system';
import {
BitbucketProject,
BitbucketProjectRepositories,
} 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 BitbucketImportRepositoryForm from './BitbucketImportRepositoryForm';
almInstances: AlmSettingsInstance[];
canAdmin?: boolean;
loading: boolean;
- onImportRepository: () => void;
+ onImportRepository: (repository: BitbucketRepository) => void;
onSearch: (query: string) => void;
- onSelectRepository: (repo: BitbucketRepository) => void;
onPersonalAccessTokenCreated: () => void;
onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void;
projects?: BitbucketProject[];
resetPat: boolean;
searching: boolean;
searchResults?: BitbucketRepository[];
- selectedRepository?: BitbucketRepository;
showPersonalAccessTokenForm?: boolean;
}
loading,
projects,
projectRepositories,
- selectedRepository,
+
searching,
searchResults,
showPersonalAccessTokenForm,
} = props;
return (
- <>
- <CreateProjectPageHeader
- additionalActions={
- !showPersonalAccessTokenForm && (
- <div className="display-flex-center pull-right">
- <Button
- className="button-large button-primary"
- disabled={!selectedRepository}
- onClick={props.onImportRepository}
- >
- {translate('onboarding.create_project.import_selected_repo')}
- </Button>
- </div>
- )
- }
- 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.from_bbs')}
- </span>
- }
- />
+ <PageContentFontWrapper>
+ <header className="sw-mb-10">
+ <Title className="sw-mb-4">
+ {translate('onboarding.create_project.import_selected_repo')}
+ </Title>
+ <LightPrimary className="sw-body-sm">
+ {translate('onboarding.create_project.from_bbs')}
+ </LightPrimary>
+ </header>
<AlmSettingsInstanceDropdown
almKey={AlmKeys.BitbucketServer}
onChangeConfig={props.onSelectedAlmInstanceChange}
/>
- {loading && <i className="spinner" />}
+ <DeferredSpinner loading={loading}>
+ {!selectedAlmInstance && (
+ <WrongBindingCountAlert alm={AlmKeys.BitbucketServer} canAdmin={!!canAdmin} />
+ )}
- {!loading && !selectedAlmInstance && (
- <WrongBindingCountAlert alm={AlmKeys.BitbucketServer} canAdmin={!!canAdmin} />
- )}
-
- {!loading &&
- selectedAlmInstance &&
- (showPersonalAccessTokenForm ? (
- <PersonalAccessTokenForm
- almSetting={selectedAlmInstance}
- onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated}
- resetPat={resetPat}
- />
- ) : (
- <BitbucketImportRepositoryForm
- onSearch={props.onSearch}
- onSelectRepository={props.onSelectRepository}
- projectRepositories={projectRepositories}
- projects={projects}
- searchResults={searchResults}
- searching={searching}
- selectedRepository={selectedRepository}
- />
- ))}
- </>
+ {selectedAlmInstance &&
+ (showPersonalAccessTokenForm ? (
+ <PersonalAccessTokenForm
+ almSetting={selectedAlmInstance}
+ onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated}
+ resetPat={resetPat}
+ />
+ ) : (
+ <BitbucketImportRepositoryForm
+ onSearch={props.onSearch}
+ projectRepositories={projectRepositories}
+ projects={projects}
+ searchResults={searchResults}
+ searching={searching}
+ onImportRepository={props.onImportRepository}
+ />
+ ))}
+ </DeferredSpinner>
+ </PageContentFontWrapper>
);
}
*/
import { uniq, without } from 'lodash';
import * as React from 'react';
-import { ButtonLink } from '../../../../components/controls/buttons';
-import { translate } from '../../../../helpers/l10n';
import {
BitbucketProject,
BitbucketProjectRepositories,
import BitbucketProjectAccordion from './BitbucketProjectAccordion';
export interface BitbucketRepositoriesProps {
- onSelectRepository: (repo: BitbucketRepository) => void;
+ onImportRepository: (repo: BitbucketRepository) => void;
projects: BitbucketProject[];
projectRepositories: BitbucketProjectRepositories;
- selectedRepository?: BitbucketRepository;
}
export default function BitbucketRepositories(props: BitbucketRepositoriesProps) {
- const { projects, projectRepositories, selectedRepository } = props;
+ const { projects, projectRepositories } = props;
const [openProjectKeys, setOpenProjectKeys] = React.useState(
projects.length > 0 ? [projects[0].key] : []
);
- const allAreExpanded = projects.length <= openProjectKeys.length;
-
const handleClick = (isOpen: boolean, projectKey: string) => {
setOpenProjectKeys(
isOpen ? without(openProjectKeys, projectKey) : uniq([...openProjectKeys, projectKey])
return (
<>
- <div className="overflow-hidden spacer-bottom">
- <ButtonLink
- className="pull-right"
- onClick={() => setOpenProjectKeys(allAreExpanded ? [] : projects.map((p) => p.key))}
- >
- {allAreExpanded ? translate('collapse_all') : translate('expand_all')}
- </ButtonLink>
- </div>
-
{projects.map((project) => {
const isOpen = openProjectKeys.includes(project.key);
const { allShown, repositories = [] } = projectRepositories[project.key] || {};
<BitbucketProjectAccordion
key={project.key}
onClick={() => handleClick(isOpen, project.key)}
- onSelectRepository={props.onSelectRepository}
open={isOpen}
project={project}
repositories={repositories}
- selectedRepository={selectedRepository}
showingAllRepositories={allShown}
+ onImportRepository={props.onImportRepository}
/>
);
})}
* 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, FlagMessage } from 'design-system';
import * as React from 'react';
-import { Alert } from '../../../../components/ui/Alert';
-import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
import { translate } from '../../../../helpers/l10n';
import { BitbucketProject, BitbucketRepository } from '../../../../types/alm-integration';
import BitbucketProjectAccordion from './BitbucketProjectAccordion';
export interface BitbucketSearchResultsProps {
- onSelectRepository: (repo: BitbucketRepository) => void;
+ onImportRepository: (repo: BitbucketRepository) => void;
projects: BitbucketProject[];
searching: boolean;
searchResults?: BitbucketRepository[];
- selectedRepository?: BitbucketRepository;
}
export default function BitbucketSearchResults(props: BitbucketSearchResultsProps) {
- const { projects, searching, searchResults = [], selectedRepository } = props;
+ const { projects, searching, searchResults = [] } = props;
if (searchResults.length === 0 && !searching) {
return (
- <Alert className="big-spacer-top" variant="warning">
+ <FlagMessage variant="warning">
{translate('onboarding.create_project.no_bbs_repos.filter')}
- </Alert>
+ </FlagMessage>
);
}
);
return (
- <div className="big-spacer-top">
- <DeferredSpinner loading={searching}>
- {filteredSearchResults.length > 0 && (
+ <DeferredSpinner loading={searching}>
+ {filteredSearchResults.length > 0 && (
+ <BitbucketProjectAccordion
+ onImportRepository={props.onImportRepository}
+ open
+ repositories={filteredSearchResults}
+ showingAllRepositories
+ />
+ )}
+
+ {filteredProjects.map((project) => {
+ const repositories = searchResults.filter((r) => r.projectKey === project.key);
+
+ return (
<BitbucketProjectAccordion
- onSelectRepository={props.onSelectRepository}
+ onImportRepository={props.onImportRepository}
+ key={project.key}
open
- repositories={filteredSearchResults}
- selectedRepository={selectedRepository}
+ project={project}
+ repositories={repositories}
showingAllRepositories
/>
- )}
-
- {filteredProjects.map((project) => {
- const repositories = searchResults.filter((r) => r.projectKey === project.key);
-
- return (
- <BitbucketProjectAccordion
- key={project.key}
- onSelectRepository={props.onSelectRepository}
- open
- project={project}
- repositories={repositories}
- selectedRepository={selectedRepository}
- showingAllRepositories
- />
- );
- })}
- </DeferredSpinner>
- </div>
+ );
+ })}
+ </DeferredSpinner>
);
}
expect(screen.getByText('Bitbucket Project 1')).toBeInTheDocument();
- await user.click(screen.getByRole('button', { name: 'expand_all' }));
-
- expect(screen.getByRole('button', { name: 'collapse_all' })).toBeInTheDocument();
-
const projectItem = screen.getByRole('region', { name: /Bitbucket Project 1/ });
expect(
);
await user.click(projectItem);
- const radioButton = within(projectItem).getByRole('radio', {
- name: 'Bitbucket Repo 2',
- });
- const importButton = screen.getByText('onboarding.create_project.import_selected_repo');
- expect(radioButton).toBeInTheDocument();
- expect(importButton).toBeDisabled();
- await user.click(radioButton);
- expect(importButton).toBeEnabled();
+ expect(
+ screen.getByRole('row', {
+ name: 'Bitbucket Repo 1 onboarding.create_project.repository_imported',
+ })
+ ).toBeInTheDocument();
+
+ expect(
+ screen.getByRole('row', {
+ name: 'Bitbucket Repo 2 onboarding.create_project.import',
+ })
+ ).toBeInTheDocument();
+
+ const importButton = screen.getByText('onboarding.create_project.import');
await user.click(importButton);
expect(
my_account.add_project=Add Project
my_account.add_project.manual=Manually
my_account.add_project.azure=From Azure DevOps
-my_account.add_project.bitbucket=from Bitbucket Server
+my_account.add_project.bitbucket=From Bitbucket Server
my_account.add_project.bitbucketcloud=From Bitbucket Cloud
my_account.add_project.github=From GitHub
my_account.add_project.gitlab=From GitLab