Browse Source

SONAR-13027 Implement search for Bitbucket Server repositories

tags/8.2.0.32929
Wouter Admiraal 4 years ago
parent
commit
b054a6785b
22 changed files with 1550 additions and 576 deletions
  1. 13
    0
      server/sonar-web/src/main/js/api/alm-integrations.ts
  2. 8
    0
      server/sonar-web/src/main/js/app/styles/components/boxed-group.css
  3. 0
    5
      server/sonar-web/src/main/js/app/styles/init/misc.css
  4. 46
    89
      server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx
  5. 129
    0
      server/sonar-web/src/main/js/apps/create/project/BitbucketProjectAccordion.tsx
  6. 51
    8
      server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx
  7. 17
    5
      server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx
  8. 84
    0
      server/sonar-web/src/main/js/apps/create/project/BitbucketRepositories.tsx
  9. 77
    0
      server/sonar-web/src/main/js/apps/create/project/BitbucketSearchResults.tsx
  10. 21
    33
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketImportRepositoryForm-test.tsx
  11. 76
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectAccordion-test.tsx
  12. 32
    5
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreate-test.tsx
  13. 3
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreateRenderer-test.tsx
  14. 66
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketRepositories-test.tsx
  15. 48
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketSearchResults-test.tsx
  16. 94
    403
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketImportRepositoryForm-test.tsx.snap
  17. 434
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectAccordion-test.tsx.snap
  18. 2
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreate-test.tsx.snap
  19. 42
    27
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap
  20. 248
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketRepositories-test.tsx.snap
  21. 57
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketSearchResults-test.tsx.snap
  22. 2
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 13
- 0
server/sonar-web/src/main/js/api/alm-integrations.ts View File

@@ -68,3 +68,16 @@ export function importBitbucketServerProject(
repositorySlug
}).catch(throwGlobalError);
}

export function searchForBitbucketServerRepositories(
almSetting: string,
repositoryName: string
): Promise<{
isLastPage: boolean;
repositories: BitbucketRepository[];
}> {
return getJSON('/api/alm_integrations/search_bitbucketserver_repos', {
almSetting,
repositoryName
});
}

+ 8
- 0
server/sonar-web/src/main/js/app/styles/components/boxed-group.css View File

@@ -104,6 +104,14 @@
padding-bottom: calc(2 * var(--gridSize));
}

.boxed-group-accordion.not-clickable .boxed-group-header {
cursor: default;
}

.boxed-group-accordion.not-clickable .boxed-group-accordion-title > svg {
display: none;
}

.boxed-group-accordion-alert.boxed-group-accordion-alert {
vertical-align: middle;
margin-bottom: -6px;

+ 0
- 5
server/sonar-web/src/main/js/app/styles/init/misc.css View File

@@ -407,11 +407,6 @@ th.huge-spacer-right {
align-items: center;
}

.display-inline-flex-start {
display: inline-flex !important;
align-items: start;
}

.position-absolute {
position: absolute !important;
}

+ 46
- 89
server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx View File

@@ -17,115 +17,72 @@
* 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 classNames from 'classnames';
import { uniq, without } from 'lodash';
import * as React from 'react';
import { Link } from 'react-router';
import BoxedGroupAccordion from 'sonar-ui-common/components/controls/BoxedGroupAccordion';
import { ButtonLink } from 'sonar-ui-common/components/controls/buttons';
import Radio from 'sonar-ui-common/components/controls/Radio';
import CheckIcon from 'sonar-ui-common/components/icons/CheckIcon';
import SearchBox from 'sonar-ui-common/components/controls/SearchBox';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { colors } from '../../../app/theme';
import { getProjectUrl } from '../../../helpers/urls';
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration';
import {
BitbucketProject,
BitbucketProjectRepositories,
BitbucketRepository
} from '../../../types/alm-integration';
import BitbucketRepositories from './BitbucketRepositories';
import BitbucketSearchResults from './BitbucketSearchResults';

export interface BitbucketImportRepositoryFormProps {
importing?: boolean;
disableRepositories: boolean;
onSearch: (query: string) => void;
onSelectRepository: (repo: BitbucketRepository) => void;
projects?: BitbucketProject[];
projectRepositories?: T.Dict<BitbucketRepository[]>;
projectRepositories?: BitbucketProjectRepositories;
searching: boolean;
searchResults?: BitbucketRepository[];
selectedRepository?: BitbucketRepository;
}

export default function BitbucketImportRepositoryForm(props: BitbucketImportRepositoryFormProps) {
const { importing, projects = [], projectRepositories = {}, selectedRepository } = props;
const [openProjectKeys, setOpenProjectKeys] = React.useState(
projects.length > 0 ? [projects[0].key] : []
);
const {
disableRepositories,
projects = [],
projectRepositories = {},
searchResults,
searching,
selectedRepository
} = props;

if (projects.length === 0) {
return (
<Alert variant="warning">{translate('onboarding.create_project.no_bbs_projects')}</Alert>
<Alert className="spacer-top" variant="warning">
{translate('onboarding.create_project.no_bbs_projects')}
</Alert>
);
}

const allAreExpanded = projects.length === openProjectKeys.length;

return (
<div className="create-project-import-bbs">
<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 repositories = projectRepositories[project.key] || [];

return (
<BoxedGroupAccordion
className={classNames({ open: isOpen })}
key={project.key}
onClick={() =>
setOpenProjectKeys(
isOpen
? without(openProjectKeys, project.key)
: uniq([...openProjectKeys, project.key])
)
}
open={isOpen}
title={<h3>{project.name}</h3>}>
{isOpen && (
<div className="display-flex-wrap">
{repositories.length === 0 && (
<Alert variant="warning">
{translate('onboarding.create_project.no_bbs_repos')}
</Alert>
)}
<SearchBox
onChange={props.onSearch}
placeholder={translate('onboarding.create_project.search_repositories_by_name')}
/>

{repositories.map(repo =>
repo.sqProjectKey ? (
<span
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key={repo.id}>
<CheckIcon className="spacer-right" fill={colors.green} size={14} />
<span>
<div className="little-spacer-bottom">
<strong>
<Link to={getProjectUrl(repo.sqProjectKey)}>{repo.name}</Link>
</strong>
</div>
<em>{translate('onboarding.create_project.repository_imported')}</em>
</span>
</span>
) : (
<Radio
checked={selectedRepository?.id === repo.id}
className={classNames(
'display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden',
{
disabled: importing,
'text-muted': importing,
'link-no-underline': importing
}
)}
key={repo.id}
onCheck={() => props.onSelectRepository(repo)}
value={String(repo.id)}>
<strong className="text-ellipsis">{repo.name}</strong>
</Radio>
)
)}
</div>
)}
</BoxedGroupAccordion>
);
})}
{searching || searchResults ? (
<BitbucketSearchResults
disableRepositories={disableRepositories}
onSelectRepository={props.onSelectRepository}
projects={projects}
searchResults={searchResults}
searching={searching}
selectedRepository={selectedRepository}
/>
) : (
<BitbucketRepositories
disableRepositories={disableRepositories}
onSelectRepository={props.onSelectRepository}
projectRepositories={projectRepositories}
projects={projects}
selectedRepository={selectedRepository}
/>
)}
</div>
);
}

+ 129
- 0
server/sonar-web/src/main/js/apps/create/project/BitbucketProjectAccordion.tsx View File

@@ -0,0 +1,129 @@
/*
* SonarQube
* Copyright (C) 2009-2020 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 classNames from 'classnames';
import * as React from 'react';
import { Link } from 'react-router';
import BoxedGroupAccordion from 'sonar-ui-common/components/controls/BoxedGroupAccordion';
import Radio from 'sonar-ui-common/components/controls/Radio';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import CheckIcon from 'sonar-ui-common/components/icons/CheckIcon';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { colors } from '../../../app/theme';
import { getProjectUrl } from '../../../helpers/urls';
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration';

export interface BitbucketProjectAccordionProps {
disableRepositories: boolean;
onClick?: () => void;
onSelectRepository: (repo: BitbucketRepository) => void;
open: boolean;
project: BitbucketProject;
repositories: BitbucketRepository[];
selectedRepository?: BitbucketRepository;
showingAllRepositories: boolean;
}

export default function BitbucketProjectAccordion(props: BitbucketProjectAccordionProps) {
const {
disableRepositories,
open,
project,
repositories,
selectedRepository,
showingAllRepositories
} = props;

const repositoryCount = repositories.length;

return (
<BoxedGroupAccordion
className={classNames('big-spacer-bottom', {
open,
'not-clickable': !props.onClick,
'no-hover': !props.onClick
})}
key={project.key}
onClick={
props.onClick
? props.onClick
: () => {
/* noop */
}
}
open={open}
title={<h3>{project.name}</h3>}>
{open && (
<div className="display-flex-wrap">
{repositoryCount === 0 && (
<Alert variant="warning">{translate('onboarding.create_project.no_bbs_repos')}</Alert>
)}

{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 text-ellipsis">
<Tooltip overlay={repo.name}>
<strong>
<Link to={getProjectUrl(repo.sqProjectKey)}>{repo.name}</Link>
</strong>
</Tooltip>
</div>
<em>{translate('onboarding.create_project.repository_imported')}</em>
</div>
</div>
) : (
<Radio
checked={selectedRepository?.id === repo.id}
className={classNames(
'display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden',
{
disabled: disableRepositories,
'text-muted': disableRepositories,
'link-no-underline': disableRepositories
}
)}
key={repo.id}
onCheck={() => props.onSelectRepository(repo)}
value={String(repo.id)}>
<Tooltip overlay={repo.name}>
<strong className="text-ellipsis">{repo.name}</strong>
</Tooltip>
</Radio>
)
)}

{!showingAllRepositories && repositoryCount > 0 && (
<Alert variant="warning">
{translateWithParameters(
'onboarding.create_project.only_showing_X_first_repos',
repositoryCount
)}
</Alert>
)}
</div>
)}
</BoxedGroupAccordion>
);
}

+ 51
- 8
server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx View File

@@ -25,10 +25,15 @@ import {
getBitbucketServerProjects,
getBitbucketServerRepositories,
importBitbucketServerProject,
searchForBitbucketServerRepositories,
setAlmPersonalAccessToken
} from '../../../api/alm-integrations';
import { getAppState, Store } from '../../../store/rootReducer';
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration';
import {
BitbucketProject,
BitbucketProjectRepositories,
BitbucketRepository
} from '../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../types/alm-settings';
import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer';

@@ -45,7 +50,9 @@ interface State {
loading: boolean;
patIsValid?: boolean;
projects?: BitbucketProject[];
projectRepositories?: T.Dict<BitbucketRepository[]>;
projectRepositories?: BitbucketProjectRepositories;
searching: boolean;
searchResults?: BitbucketRepository[];
selectedRepository?: BitbucketRepository;
submittingToken?: boolean;
}
@@ -60,7 +67,8 @@ export class BitbucketProjectCreate extends React.PureComponent<Props, State> {
// one from the list.
bitbucketSetting: props.bitbucketSettings[0],
importing: false,
loading: false
loading: false,
searching: false
};
}

@@ -127,7 +135,7 @@ export class BitbucketProjectCreate extends React.PureComponent<Props, State> {

fetchBitbucketRepositories = (
projects: BitbucketProject[]
): Promise<T.Dict<BitbucketRepository[]> | undefined> => {
): Promise<BitbucketProjectRepositories | undefined> => {
const { bitbucketSetting } = this.state;

if (!bitbucketSetting) {
@@ -137,16 +145,20 @@ export class BitbucketProjectCreate extends React.PureComponent<Props, State> {
return Promise.all(
projects.map(p => {
return getBitbucketServerRepositories(bitbucketSetting.key, p.name).then(
({ repositories }) => ({
({ isLastPage, repositories }) => ({
isLastPage,
repositories,
projectKey: p.key
})
);
})
).then(results => {
return results.reduce((acc: T.Dict<BitbucketRepository[]>, { projectKey, repositories }) => {
return { ...acc, [projectKey]: repositories };
}, {});
return results.reduce(
(acc: BitbucketProjectRepositories, { isLastPage, projectKey, repositories }) => {
return { ...acc, [projectKey]: { allShown: isLastPage, repositories } };
},
{}
);
});
};

@@ -198,6 +210,32 @@ export class BitbucketProjectCreate extends React.PureComponent<Props, State> {
});
};

handleSearch = (query: string) => {
const { bitbucketSetting } = this.state;

if (!bitbucketSetting) {
return;
}

if (!query) {
this.setState({ searching: false, searchResults: undefined, selectedRepository: undefined });
return;
}

this.setState({ searching: true, selectedRepository: undefined });
searchForBitbucketServerRepositories(bitbucketSetting.key, query)
.then(({ repositories }) => {
if (this.mounted) {
this.setState({ searching: false, searchResults: repositories });
}
})
.catch(() => {
if (this.mounted) {
this.setState({ searching: false });
}
});
};

handleSelectRepository = (selectedRepository: BitbucketRepository) => {
this.setState({ selectedRepository });
};
@@ -211,6 +249,8 @@ export class BitbucketProjectCreate extends React.PureComponent<Props, State> {
patIsValid,
projectRepositories,
projects,
searching,
searchResults,
selectedRepository,
submittingToken
} = this.state;
@@ -224,9 +264,12 @@ export class BitbucketProjectCreate extends React.PureComponent<Props, State> {
onImportRepository={this.handleImportRepository}
onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate}
onProjectCreate={this.props.onProjectCreate}
onSearch={this.handleSearch}
onSelectRepository={this.handleSelectRepository}
projectRepositories={projectRepositories}
projects={projects}
searchResults={searchResults}
searching={searching}
selectedRepository={selectedRepository}
showPersonalAccessTokenForm={!patIsValid}
submittingToken={submittingToken}

+ 17
- 5
server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx View File

@@ -25,9 +25,13 @@ import { Alert } from 'sonar-ui-common/components/ui/Alert';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration';
import {
BitbucketProject,
BitbucketProjectRepositories,
BitbucketRepository
} from '../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../types/alm-settings';
import { PULL_REQUEST_DECORATION_BINDING_CATEGORY } from '../../settings/components/AdditionalCategoryKeys';
import { ALM_INTEGRATION } from '../../settings/components/AdditionalCategoryKeys';
import BitbucketImportRepositoryForm from './BitbucketImportRepositoryForm';
import BitbucketPersonalAccessTokenForm from './BitbucketPersonalAccessTokenForm';
import CreateProjectPageHeader from './CreateProjectPageHeader';
@@ -38,11 +42,14 @@ export interface BitbucketProjectCreateRendererProps {
importing: boolean;
loading: boolean;
onImportRepository: () => void;
onSearch: (query: string) => void;
onSelectRepository: (repo: BitbucketRepository) => void;
onPersonalAccessTokenCreate: (token: string) => void;
onProjectCreate: (projectKeys: string[]) => void;
projects?: BitbucketProject[];
projectRepositories?: T.Dict<BitbucketRepository[]>;
projectRepositories?: BitbucketProjectRepositories;
searching: boolean;
searchResults?: BitbucketRepository[];
selectedRepository?: BitbucketRepository;
showPersonalAccessTokenForm?: boolean;
submittingToken?: boolean;
@@ -57,6 +64,8 @@ export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCr
projects,
projectRepositories,
selectedRepository,
searching,
searchResults,
showPersonalAccessTokenForm,
submittingToken
} = props;
@@ -104,7 +113,7 @@ export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCr
<Link
to={{
pathname: '/admin/settings',
query: { category: PULL_REQUEST_DECORATION_BINDING_CATEGORY }
query: { category: ALM_INTEGRATION }
}}>
{translate('settings.page')}
</Link>
@@ -127,10 +136,13 @@ export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCr
/>
) : (
<BitbucketImportRepositoryForm
importing={importing}
disableRepositories={importing}
onSearch={props.onSearch}
onSelectRepository={props.onSelectRepository}
projectRepositories={projectRepositories}
projects={projects}
searchResults={searchResults}
searching={searching}
selectedRepository={selectedRepository}
/>
))}

+ 84
- 0
server/sonar-web/src/main/js/apps/create/project/BitbucketRepositories.tsx View File

@@ -0,0 +1,84 @@
/*
* SonarQube
* Copyright (C) 2009-2020 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 { uniq, without } from 'lodash';
import * as React from 'react';
import { ButtonLink } from 'sonar-ui-common/components/controls/buttons';
import { translate } from 'sonar-ui-common/helpers/l10n';
import {
BitbucketProject,
BitbucketProjectRepositories,
BitbucketRepository
} from '../../../types/alm-integration';
import BitbucketProjectAccordion from './BitbucketProjectAccordion';

export interface BitbucketRepositoriesProps {
disableRepositories: boolean;
onSelectRepository: (repo: BitbucketRepository) => void;
projects: BitbucketProject[];
projectRepositories: BitbucketProjectRepositories;
selectedRepository?: BitbucketRepository;
}

export default function BitbucketRepositories(props: BitbucketRepositoriesProps) {
const { disableRepositories, projects, projectRepositories, selectedRepository } = 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] || {};

return (
<BitbucketProjectAccordion
disableRepositories={disableRepositories}
key={project.key}
onClick={() => handleClick(isOpen, project.key)}
onSelectRepository={props.onSelectRepository}
open={isOpen}
project={project}
repositories={repositories}
selectedRepository={selectedRepository}
showingAllRepositories={allShown}
/>
);
})}
</>
);
}

+ 77
- 0
server/sonar-web/src/main/js/apps/create/project/BitbucketSearchResults.tsx View File

@@ -0,0 +1,77 @@
/*
* SonarQube
* Copyright (C) 2009-2020 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 { uniq } from 'lodash';
import * as React from 'react';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { isDefined } from 'sonar-ui-common/helpers/types';
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration';
import BitbucketProjectAccordion from './BitbucketProjectAccordion';

export interface BitbucketSearchResultsProps {
disableRepositories: boolean;
onSelectRepository: (repo: BitbucketRepository) => void;
projects: BitbucketProject[];
searching: boolean;
searchResults?: BitbucketRepository[];
selectedRepository?: BitbucketRepository;
}

export default function BitbucketSearchResults(props: BitbucketSearchResultsProps) {
const {
disableRepositories,
projects,
searching,
searchResults = [],
selectedRepository
} = props;

const filteredProjects = uniq(
searchResults.map(r => projects.find(p => p.key === r.projectKey)).filter(isDefined)
);

return filteredProjects.length === 0 && !searching ? (
<Alert className="big-spacer-top" variant="warning">
{translate('onboarding.create_project.no_bbs_repos.filter')}
</Alert>
) : (
<div className="big-spacer-top">
<DeferredSpinner loading={searching}>
{filteredProjects.map(project => {
const repositories = searchResults.filter(r => r.projectKey === project.key);

return (
<BitbucketProjectAccordion
disableRepositories={disableRepositories}
key={project.key}
onSelectRepository={props.onSelectRepository}
open={true}
project={project}
repositories={repositories}
selectedRepository={selectedRepository}
showingAllRepositories={true}
/>
);
})}
</DeferredSpinner>
</div>
);
}

+ 21
- 33
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketImportRepositoryForm-test.tsx View File

@@ -20,9 +20,8 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { shallow } from 'enzyme';
import * as React from 'react';
import BoxedGroupAccordion from 'sonar-ui-common/components/controls/BoxedGroupAccordion';
import Radio from 'sonar-ui-common/components/controls/Radio';
import { click } from 'sonar-ui-common/helpers/testUtils';
import SearchBox from 'sonar-ui-common/components/controls/SearchBox';
import { change } from 'sonar-ui-common/helpers/testUtils';
import {
mockBitbucketProject,
mockBitbucketRepository
@@ -33,51 +32,40 @@ import BitbucketImportRepositoryForm, {

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ importing: true })).toMatchSnapshot('importing');
expect(shallowRender({ projects: [] })).toMatchSnapshot('no projects');
expect(shallowRender({ projectRepositories: {} })).toMatchSnapshot('no repos');
expect(shallowRender({ selectedRepository: mockBitbucketRepository() })).toMatchSnapshot(
'selected repo'
expect(shallowRender({ projects: undefined })).toMatchSnapshot('no projects');
expect(shallowRender({ searching: true })).toMatchSnapshot('searching');
expect(shallowRender({ searchResults: [mockBitbucketRepository()] })).toMatchSnapshot(
'search results'
);
});

it('should correctly handle opening/closing accordions', () => {
const wrapper = shallowRender();
click(wrapper.find(BoxedGroupAccordion).at(1));
expect(wrapper).toMatchSnapshot('2nd opened');
});

it('should correctly handle selecting repos', () => {
const onSelectRepository = jest.fn();
const repo = mockBitbucketRepository();
const wrapper = shallowRender({
onSelectRepository,
projectRepositories: {
project: [repo]
}
});

wrapper
.find(Radio)
.at(0)
.prop<Function>('onCheck')();
expect(onSelectRepository).toBeCalledWith(repo);
it('should correctly handle search', () => {
const onSearch = jest.fn();
const wrapper = shallowRender({ onSearch });
change(wrapper.find(SearchBox), 'foo');
expect(onSearch).toBeCalledWith('foo');
});

function shallowRender(props: Partial<BitbucketImportRepositoryFormProps> = {}) {
return shallow<BitbucketImportRepositoryFormProps>(
<BitbucketImportRepositoryForm
disableRepositories={false}
onSearch={jest.fn()}
onSelectRepository={jest.fn()}
projectRepositories={{
project: [
mockBitbucketRepository(),
mockBitbucketRepository({ id: 2, slug: 'bar', name: 'Bar', sqProjectKey: 'bar' })
]
project: {
allShown: true,
repositories: [
mockBitbucketRepository(),
mockBitbucketRepository({ id: 2, slug: 'bar', name: 'Bar', sqProjectKey: 'bar' })
]
}
}}
projects={[
mockBitbucketProject(),
mockBitbucketProject({ id: 2, key: 'project2', name: 'Project 2' })
]}
searching={false}
{...props}
/>
);

+ 76
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectAccordion-test.tsx View File

@@ -0,0 +1,76 @@
/*
* SonarQube
* Copyright (C) 2009-2020 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.
*/
/* eslint-disable sonarjs/no-duplicate-string */
import { shallow } from 'enzyme';
import * as React from 'react';
import Radio from 'sonar-ui-common/components/controls/Radio';
import {
mockBitbucketProject,
mockBitbucketRepository
} from '../../../../helpers/mocks/alm-integrations';
import BitbucketProjectAccordion, {
BitbucketProjectAccordionProps
} from '../BitbucketProjectAccordion';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ disableRepositories: true })).toMatchSnapshot('disable options');
expect(shallowRender({ open: false })).toMatchSnapshot('closed');
expect(shallowRender({ onClick: undefined })).toMatchSnapshot('no click handler');
expect(shallowRender({ repositories: [] })).toMatchSnapshot('no repos');
expect(shallowRender({ selectedRepository: mockBitbucketRepository() })).toMatchSnapshot(
'selected repo'
);
expect(shallowRender({ showingAllRepositories: false })).toMatchSnapshot('not showing all repos');
});

it('should correctly handle selecting repos', () => {
const onSelectRepository = jest.fn();
const repo = mockBitbucketRepository();
const wrapper = shallowRender({
onSelectRepository,
repositories: [repo]
});

wrapper
.find(Radio)
.at(0)
.props()
.onCheck('');
expect(onSelectRepository).toBeCalledWith(repo);
});

function shallowRender(props: Partial<BitbucketProjectAccordionProps> = {}) {
return shallow<BitbucketProjectAccordionProps>(
<BitbucketProjectAccordion
disableRepositories={false}
onClick={jest.fn()}
onSelectRepository={jest.fn()}
open={true}
project={mockBitbucketProject()}
repositories={[
mockBitbucketRepository(),
mockBitbucketRepository({ id: 2, slug: 'bar', name: 'Bar', sqProjectKey: 'bar' })
]}
showingAllRepositories={true}
{...props}
/>
);
}

+ 32
- 5
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreate-test.tsx View File

@@ -26,6 +26,7 @@ import {
getBitbucketServerProjects,
getBitbucketServerRepositories,
importBitbucketServerProject,
searchForBitbucketServerRepositories,
setAlmPersonalAccessToken
} from '../../../../api/alm-integrations';
import { mockBitbucketRepository } from '../../../../helpers/mocks/alm-integrations';
@@ -53,7 +54,13 @@ jest.mock('../../../../api/alm-integrations', () => {
]
}),
importBitbucketServerProject: jest.fn().mockResolvedValue({ project: { key: 'baz' } }),
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null)
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null),
searchForBitbucketServerRepositories: jest.fn().mockResolvedValue({
repositories: [
mockBitbucketRepository(),
mockBitbucketRepository({ id: 2, slug: 'project__repo2' })
]
})
};
});

@@ -104,10 +111,12 @@ it('should correctly fetch projects and repos', async () => {
expect(getBitbucketServerRepositories).toBeCalledWith('foo', 'Project 1');
expect(wrapper.state().projectRepositories).toEqual(
expect.objectContaining({
project1: expect.arrayContaining([
expect.objectContaining({ id: 1 }),
expect.objectContaining({ id: 2 })
])
project1: expect.objectContaining({
repositories: expect.arrayContaining([
expect.objectContaining({ id: 1 }),
expect.objectContaining({ id: 2 })
])
})
})
);
expect(wrapper.state().projectRepositories).toBeDefined();
@@ -126,6 +135,24 @@ it('should correctly import a repo', async () => {
expect(onProjectCreate).toBeCalledWith(['baz']);
});

it('should correctly handle search', async () => {
const wrapper = shallowRender();
const instance = wrapper.instance();

// Don't trigger search on empty query.
instance.handleSearch('');
expect(searchForBitbucketServerRepositories).not.toBeCalled();
expect(wrapper.state().searching).toBe(false);
expect(wrapper.state().searchResults).toBeUndefined();

instance.handleSearch('bar');
expect(searchForBitbucketServerRepositories).toBeCalledWith('foo', 'bar');
expect(wrapper.state().searching).toBe(true);
await waitAndUpdate(wrapper);
expect(wrapper.state().searching).toBe(false);
expect(wrapper.state().searchResults).toHaveLength(2);
});

function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) {
return shallow<BitbucketProjectCreate>(
<BitbucketProjectCreate

+ 3
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreateRenderer-test.tsx View File

@@ -55,9 +55,11 @@ function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {})
onImportRepository={jest.fn()}
onPersonalAccessTokenCreate={jest.fn()}
onProjectCreate={jest.fn()}
onSearch={jest.fn()}
onSelectRepository={jest.fn()}
projectRepositories={{ foo: [mockBitbucketRepository()] }}
projectRepositories={{ foo: { allShown: true, repositories: [mockBitbucketRepository()] } }}
projects={[mockBitbucketProject({ key: 'foo' })]}
searching={false}
{...props}
/>
);

+ 66
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketRepositories-test.tsx View File

@@ -0,0 +1,66 @@
/*
* SonarQube
* Copyright (C) 2009-2020 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.
*/
/* eslint-disable sonarjs/no-duplicate-string */
import { shallow } from 'enzyme';
import * as React from 'react';
import { click } from 'sonar-ui-common/helpers/testUtils';
import {
mockBitbucketProject,
mockBitbucketRepository
} from '../../../../helpers/mocks/alm-integrations';
import BitbucketProjectAccordion from '../BitbucketProjectAccordion';
import BitbucketRepositories, { BitbucketRepositoriesProps } from '../BitbucketRepositories';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ projectRepositories: {} })).toMatchSnapshot('no repos');
expect(shallowRender({ selectedRepository: mockBitbucketRepository() })).toMatchSnapshot(
'selected repo'
);
});

it('should correctly handle opening/closing accordions', () => {
const wrapper = shallowRender();
click(wrapper.find(BitbucketProjectAccordion).at(1));
expect(wrapper).toMatchSnapshot('2nd opened');
});

function shallowRender(props: Partial<BitbucketRepositoriesProps> = {}) {
return shallow<BitbucketRepositoriesProps>(
<BitbucketRepositories
disableRepositories={false}
onSelectRepository={jest.fn()}
projectRepositories={{
project: {
allShown: true,
repositories: [
mockBitbucketRepository(),
mockBitbucketRepository({ id: 2, slug: 'bar', name: 'Bar', sqProjectKey: 'bar' })
]
}
}}
projects={[
mockBitbucketProject(),
mockBitbucketProject({ id: 2, key: 'project2', name: 'Project 2' })
]}
{...props}
/>
);
}

+ 48
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketSearchResults-test.tsx View File

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2020 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 {
mockBitbucketProject,
mockBitbucketRepository
} from '../../../../helpers/mocks/alm-integrations';
import BitbucketSearchResults, { BitbucketSearchResultsProps } from '../BitbucketSearchResults';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(
shallowRender({ searching: true, projects: undefined, searchResults: undefined })
).toMatchSnapshot('searching');
expect(shallowRender({ searchResults: undefined })).toMatchSnapshot('no results');
});

function shallowRender(props: Partial<BitbucketSearchResultsProps> = {}) {
return shallow<BitbucketSearchResultsProps>(
<BitbucketSearchResults
disableRepositories={false}
onSelectRepository={jest.fn()}
projects={[mockBitbucketProject()]}
searchResults={[mockBitbucketRepository()]}
searching={false}
{...props}
/>
);
}

+ 94
- 403
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketImportRepositoryForm-test.tsx.snap View File

@@ -1,289 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should correctly handle opening/closing accordions: 2nd opened 1`] = `
<div
className="create-project-import-bbs"
>
<div
className="overflow-hidden spacer-bottom"
>
<ButtonLink
className="pull-right"
onClick={[Function]}
>
collapse_all
</ButtonLink>
</div>
<BoxedGroupAccordion
className="open"
key="project"
onClick={[Function]}
open={true}
title={
<h3>
Project
</h3>
}
>
<div
className="display-flex-wrap"
>
<Radio
checked={false}
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden"
key="1"
onCheck={[Function]}
value="1"
>
<strong
className="text-ellipsis"
>
Repo
</strong>
</Radio>
<span
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key="2"
>
<CheckIcon
className="spacer-right"
fill="#00aa00"
size={14}
/>
<span>
<div
className="little-spacer-bottom"
>
<strong>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "bar",
},
}
}
>
Bar
</Link>
</strong>
</div>
<em>
onboarding.create_project.repository_imported
</em>
</span>
</span>
</div>
</BoxedGroupAccordion>
<BoxedGroupAccordion
className="open"
key="project2"
onClick={[Function]}
open={true}
title={
<h3>
Project 2
</h3>
}
>
<div
className="display-flex-wrap"
>
<Alert
variant="warning"
>
onboarding.create_project.no_bbs_repos
</Alert>
</div>
</BoxedGroupAccordion>
</div>
`;

exports[`should render correctly: default 1`] = `
<div
className="create-project-import-bbs"
>
<div
className="overflow-hidden spacer-bottom"
>
<ButtonLink
className="pull-right"
onClick={[Function]}
>
expand_all
</ButtonLink>
</div>
<BoxedGroupAccordion
className="open"
key="project"
onClick={[Function]}
open={true}
title={
<h3>
Project
</h3>
}
>
<div
className="display-flex-wrap"
>
<Radio
checked={false}
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden"
key="1"
onCheck={[Function]}
value="1"
>
<strong
className="text-ellipsis"
>
Repo
</strong>
</Radio>
<span
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key="2"
>
<CheckIcon
className="spacer-right"
fill="#00aa00"
size={14}
/>
<span>
<div
className="little-spacer-bottom"
>
<strong>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "bar",
},
}
}
>
Bar
</Link>
</strong>
</div>
<em>
onboarding.create_project.repository_imported
</em>
</span>
</span>
</div>
</BoxedGroupAccordion>
<BoxedGroupAccordion
className=""
key="project2"
onClick={[Function]}
open={false}
title={
<h3>
Project 2
</h3>
}
<SearchBox
onChange={[MockFunction]}
placeholder="onboarding.create_project.search_repositories_by_name"
/>
</div>
`;

exports[`should render correctly: importing 1`] = `
<div
className="create-project-import-bbs"
>
<div
className="overflow-hidden spacer-bottom"
>
<ButtonLink
className="pull-right"
onClick={[Function]}
>
expand_all
</ButtonLink>
</div>
<BoxedGroupAccordion
className="open"
key="project"
onClick={[Function]}
open={true}
title={
<h3>
Project
</h3>
<BitbucketRepositories
disableRepositories={false}
onSelectRepository={[MockFunction]}
projectRepositories={
Object {
"project": Object {
"allShown": true,
"repositories": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
Object {
"id": 2,
"name": "Bar",
"projectKey": "project",
"slug": "bar",
"sqProjectKey": "bar",
},
],
},
}
}
>
<div
className="display-flex-wrap"
>
<Radio
checked={false}
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden disabled text-muted link-no-underline"
key="1"
onCheck={[Function]}
value="1"
>
<strong
className="text-ellipsis"
>
Repo
</strong>
</Radio>
<span
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key="2"
>
<CheckIcon
className="spacer-right"
fill="#00aa00"
size={14}
/>
<span>
<div
className="little-spacer-bottom"
>
<strong>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "bar",
},
}
}
>
Bar
</Link>
</strong>
</div>
<em>
onboarding.create_project.repository_imported
</em>
</span>
</span>
</div>
</BoxedGroupAccordion>
<BoxedGroupAccordion
className=""
key="project2"
onClick={[Function]}
open={false}
title={
<h3>
Project 2
</h3>
projects={
Array [
Object {
"id": 1,
"key": "project",
"name": "Project",
},
Object {
"id": 2,
"key": "project2",
"name": "Project 2",
},
]
}
/>
</div>
@@ -291,150 +53,79 @@ exports[`should render correctly: importing 1`] = `

exports[`should render correctly: no projects 1`] = `
<Alert
className="spacer-top"
variant="warning"
>
onboarding.create_project.no_bbs_projects
</Alert>
`;

exports[`should render correctly: no repos 1`] = `
exports[`should render correctly: search results 1`] = `
<div
className="create-project-import-bbs"
>
<div
className="overflow-hidden spacer-bottom"
>
<ButtonLink
className="pull-right"
onClick={[Function]}
>
expand_all
</ButtonLink>
</div>
<BoxedGroupAccordion
className="open"
key="project"
onClick={[Function]}
open={true}
title={
<h3>
Project
</h3>
<SearchBox
onChange={[MockFunction]}
placeholder="onboarding.create_project.search_repositories_by_name"
/>
<BitbucketSearchResults
disableRepositories={false}
onSelectRepository={[MockFunction]}
projects={
Array [
Object {
"id": 1,
"key": "project",
"name": "Project",
},
Object {
"id": 2,
"key": "project2",
"name": "Project 2",
},
]
}
>
<div
className="display-flex-wrap"
>
<Alert
variant="warning"
>
onboarding.create_project.no_bbs_repos
</Alert>
</div>
</BoxedGroupAccordion>
<BoxedGroupAccordion
className=""
key="project2"
onClick={[Function]}
open={false}
title={
<h3>
Project 2
</h3>
searchResults={
Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
]
}
searching={false}
/>
</div>
`;

exports[`should render correctly: selected repo 1`] = `
exports[`should render correctly: searching 1`] = `
<div
className="create-project-import-bbs"
>
<div
className="overflow-hidden spacer-bottom"
>
<ButtonLink
className="pull-right"
onClick={[Function]}
>
expand_all
</ButtonLink>
</div>
<BoxedGroupAccordion
className="open"
key="project"
onClick={[Function]}
open={true}
title={
<h3>
Project
</h3>
}
>
<div
className="display-flex-wrap"
>
<Radio
checked={true}
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden"
key="1"
onCheck={[Function]}
value="1"
>
<strong
className="text-ellipsis"
>
Repo
</strong>
</Radio>
<span
className="display-inline-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key="2"
>
<CheckIcon
className="spacer-right"
fill="#00aa00"
size={14}
/>
<span>
<div
className="little-spacer-bottom"
>
<strong>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "bar",
},
}
}
>
Bar
</Link>
</strong>
</div>
<em>
onboarding.create_project.repository_imported
</em>
</span>
</span>
</div>
</BoxedGroupAccordion>
<BoxedGroupAccordion
className=""
key="project2"
onClick={[Function]}
open={false}
title={
<h3>
Project 2
</h3>
<SearchBox
onChange={[MockFunction]}
placeholder="onboarding.create_project.search_repositories_by_name"
/>
<BitbucketSearchResults
disableRepositories={false}
onSelectRepository={[MockFunction]}
projects={
Array [
Object {
"id": 1,
"key": "project",
"name": "Project",
},
Object {
"id": 2,
"key": "project2",
"name": "Project 2",
},
]
}
searching={true}
/>
</div>
`;

+ 434
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectAccordion-test.tsx.snap View File

@@ -0,0 +1,434 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: closed 1`] = `
<BoxedGroupAccordion
className="big-spacer-bottom"
key="project"
onClick={[MockFunction]}
open={false}
title={
<h3>
Project
</h3>
}
/>
`;

exports[`should render correctly: default 1`] = `
<BoxedGroupAccordion
className="big-spacer-bottom open"
key="project"
onClick={[MockFunction]}
open={true}
title={
<h3>
Project
</h3>
}
>
<div
className="display-flex-wrap"
>
<Radio
checked={false}
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden"
key="1"
onCheck={[Function]}
value="1"
>
<Tooltip
overlay="Repo"
>
<strong
className="text-ellipsis"
>
Repo
</strong>
</Tooltip>
</Radio>
<div
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key="2"
>
<CheckIcon
className="spacer-right"
fill="#00aa00"
size={14}
/>
<div
className="overflow-hidden"
>
<div
className="little-spacer-bottom text-ellipsis"
>
<Tooltip
overlay="Bar"
>
<strong>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "bar",
},
}
}
>
Bar
</Link>
</strong>
</Tooltip>
</div>
<em>
onboarding.create_project.repository_imported
</em>
</div>
</div>
</div>
</BoxedGroupAccordion>
`;

exports[`should render correctly: disable options 1`] = `
<BoxedGroupAccordion
className="big-spacer-bottom open"
key="project"
onClick={[MockFunction]}
open={true}
title={
<h3>
Project
</h3>
}
>
<div
className="display-flex-wrap"
>
<Radio
checked={false}
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden disabled text-muted link-no-underline"
key="1"
onCheck={[Function]}
value="1"
>
<Tooltip
overlay="Repo"
>
<strong
className="text-ellipsis"
>
Repo
</strong>
</Tooltip>
</Radio>
<div
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key="2"
>
<CheckIcon
className="spacer-right"
fill="#00aa00"
size={14}
/>
<div
className="overflow-hidden"
>
<div
className="little-spacer-bottom text-ellipsis"
>
<Tooltip
overlay="Bar"
>
<strong>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "bar",
},
}
}
>
Bar
</Link>
</strong>
</Tooltip>
</div>
<em>
onboarding.create_project.repository_imported
</em>
</div>
</div>
</div>
</BoxedGroupAccordion>
`;

exports[`should render correctly: no click handler 1`] = `
<BoxedGroupAccordion
className="big-spacer-bottom open not-clickable no-hover"
key="project"
onClick={[Function]}
open={true}
title={
<h3>
Project
</h3>
}
>
<div
className="display-flex-wrap"
>
<Radio
checked={false}
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden"
key="1"
onCheck={[Function]}
value="1"
>
<Tooltip
overlay="Repo"
>
<strong
className="text-ellipsis"
>
Repo
</strong>
</Tooltip>
</Radio>
<div
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key="2"
>
<CheckIcon
className="spacer-right"
fill="#00aa00"
size={14}
/>
<div
className="overflow-hidden"
>
<div
className="little-spacer-bottom text-ellipsis"
>
<Tooltip
overlay="Bar"
>
<strong>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "bar",
},
}
}
>
Bar
</Link>
</strong>
</Tooltip>
</div>
<em>
onboarding.create_project.repository_imported
</em>
</div>
</div>
</div>
</BoxedGroupAccordion>
`;

exports[`should render correctly: no repos 1`] = `
<BoxedGroupAccordion
className="big-spacer-bottom open"
key="project"
onClick={[MockFunction]}
open={true}
title={
<h3>
Project
</h3>
}
>
<div
className="display-flex-wrap"
>
<Alert
variant="warning"
>
onboarding.create_project.no_bbs_repos
</Alert>
</div>
</BoxedGroupAccordion>
`;

exports[`should render correctly: not showing all repos 1`] = `
<BoxedGroupAccordion
className="big-spacer-bottom open"
key="project"
onClick={[MockFunction]}
open={true}
title={
<h3>
Project
</h3>
}
>
<div
className="display-flex-wrap"
>
<Radio
checked={false}
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden"
key="1"
onCheck={[Function]}
value="1"
>
<Tooltip
overlay="Repo"
>
<strong
className="text-ellipsis"
>
Repo
</strong>
</Tooltip>
</Radio>
<div
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key="2"
>
<CheckIcon
className="spacer-right"
fill="#00aa00"
size={14}
/>
<div
className="overflow-hidden"
>
<div
className="little-spacer-bottom text-ellipsis"
>
<Tooltip
overlay="Bar"
>
<strong>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "bar",
},
}
}
>
Bar
</Link>
</strong>
</Tooltip>
</div>
<em>
onboarding.create_project.repository_imported
</em>
</div>
</div>
<Alert
variant="warning"
>
onboarding.create_project.only_showing_X_first_repos.2
</Alert>
</div>
</BoxedGroupAccordion>
`;

exports[`should render correctly: selected repo 1`] = `
<BoxedGroupAccordion
className="big-spacer-bottom open"
key="project"
onClick={[MockFunction]}
open={true}
title={
<h3>
Project
</h3>
}
>
<div
className="display-flex-wrap"
>
<Radio
checked={true}
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo overflow-hidden"
key="1"
onCheck={[Function]}
value="1"
>
<Tooltip
overlay="Repo"
>
<strong
className="text-ellipsis"
>
Repo
</strong>
</Tooltip>
</Radio>
<div
className="display-flex-start spacer-right spacer-bottom create-project-import-bbs-repo"
key="2"
>
<CheckIcon
className="spacer-right"
fill="#00aa00"
size={14}
/>
<div
className="overflow-hidden"
>
<div
className="little-spacer-bottom text-ellipsis"
>
<Tooltip
overlay="Bar"
>
<strong>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "bar",
},
}
}
>
Bar
</Link>
</strong>
</Tooltip>
</div>
<em>
onboarding.create_project.repository_imported
</em>
</div>
</div>
</div>
</BoxedGroupAccordion>
`;

+ 2
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreate-test.tsx.snap View File

@@ -13,7 +13,9 @@ exports[`should render correctly 1`] = `
onImportRepository={[Function]}
onPersonalAccessTokenCreate={[Function]}
onProjectCreate={[MockFunction]}
onSearch={[Function]}
onSelectRepository={[Function]}
searching={false}
showPersonalAccessTokenForm={true}
/>
`;

+ 42
- 27
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap View File

@@ -37,18 +37,22 @@ exports[`should render correctly: default 1`] = `
}
/>
<BitbucketImportRepositoryForm
importing={false}
disableRepositories={false}
onSearch={[MockFunction]}
onSelectRepository={[MockFunction]}
projectRepositories={
Object {
"foo": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
],
"foo": Object {
"allShown": true,
"repositories": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
],
},
}
}
projects={
@@ -60,6 +64,7 @@ exports[`should render correctly: default 1`] = `
},
]
}
searching={false}
/>
</Fragment>
`;
@@ -101,18 +106,22 @@ exports[`should render correctly: importing 1`] = `
}
/>
<BitbucketImportRepositoryForm
importing={true}
disableRepositories={true}
onSearch={[MockFunction]}
onSelectRepository={[MockFunction]}
projectRepositories={
Object {
"foo": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
],
"foo": Object {
"allShown": true,
"repositories": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
],
},
}
}
projects={
@@ -124,6 +133,7 @@ exports[`should render correctly: importing 1`] = `
},
]
}
searching={false}
/>
</Fragment>
`;
@@ -347,18 +357,22 @@ exports[`should render correctly: selected repo 1`] = `
}
/>
<BitbucketImportRepositoryForm
importing={false}
disableRepositories={false}
onSearch={[MockFunction]}
onSelectRepository={[MockFunction]}
projectRepositories={
Object {
"foo": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
],
"foo": Object {
"allShown": true,
"repositories": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
],
},
}
}
projects={
@@ -370,6 +384,7 @@ exports[`should render correctly: selected repo 1`] = `
},
]
}
searching={false}
selectedRepository={
Object {
"id": 1,

+ 248
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketRepositories-test.tsx.snap View File

@@ -0,0 +1,248 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should correctly handle opening/closing accordions: 2nd opened 1`] = `
<Fragment>
<div
className="overflow-hidden spacer-bottom"
>
<ButtonLink
className="pull-right"
onClick={[Function]}
>
collapse_all
</ButtonLink>
</div>
<BitbucketProjectAccordion
disableRepositories={false}
key="project"
onClick={[Function]}
onSelectRepository={[MockFunction]}
open={true}
project={
Object {
"id": 1,
"key": "project",
"name": "Project",
}
}
repositories={
Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
Object {
"id": 2,
"name": "Bar",
"projectKey": "project",
"slug": "bar",
"sqProjectKey": "bar",
},
]
}
showingAllRepositories={true}
/>
<BitbucketProjectAccordion
disableRepositories={false}
key="project2"
onClick={[Function]}
onSelectRepository={[MockFunction]}
open={true}
project={
Object {
"id": 2,
"key": "project2",
"name": "Project 2",
}
}
repositories={Array []}
/>
</Fragment>
`;

exports[`should render correctly: default 1`] = `
<Fragment>
<div
className="overflow-hidden spacer-bottom"
>
<ButtonLink
className="pull-right"
onClick={[Function]}
>
expand_all
</ButtonLink>
</div>
<BitbucketProjectAccordion
disableRepositories={false}
key="project"
onClick={[Function]}
onSelectRepository={[MockFunction]}
open={true}
project={
Object {
"id": 1,
"key": "project",
"name": "Project",
}
}
repositories={
Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
Object {
"id": 2,
"name": "Bar",
"projectKey": "project",
"slug": "bar",
"sqProjectKey": "bar",
},
]
}
showingAllRepositories={true}
/>
<BitbucketProjectAccordion
disableRepositories={false}
key="project2"
onClick={[Function]}
onSelectRepository={[MockFunction]}
open={false}
project={
Object {
"id": 2,
"key": "project2",
"name": "Project 2",
}
}
repositories={Array []}
/>
</Fragment>
`;

exports[`should render correctly: no repos 1`] = `
<Fragment>
<div
className="overflow-hidden spacer-bottom"
>
<ButtonLink
className="pull-right"
onClick={[Function]}
>
expand_all
</ButtonLink>
</div>
<BitbucketProjectAccordion
disableRepositories={false}
key="project"
onClick={[Function]}
onSelectRepository={[MockFunction]}
open={true}
project={
Object {
"id": 1,
"key": "project",
"name": "Project",
}
}
repositories={Array []}
/>
<BitbucketProjectAccordion
disableRepositories={false}
key="project2"
onClick={[Function]}
onSelectRepository={[MockFunction]}
open={false}
project={
Object {
"id": 2,
"key": "project2",
"name": "Project 2",
}
}
repositories={Array []}
/>
</Fragment>
`;

exports[`should render correctly: selected repo 1`] = `
<Fragment>
<div
className="overflow-hidden spacer-bottom"
>
<ButtonLink
className="pull-right"
onClick={[Function]}
>
expand_all
</ButtonLink>
</div>
<BitbucketProjectAccordion
disableRepositories={false}
key="project"
onClick={[Function]}
onSelectRepository={[MockFunction]}
open={true}
project={
Object {
"id": 1,
"key": "project",
"name": "Project",
}
}
repositories={
Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
Object {
"id": 2,
"name": "Bar",
"projectKey": "project",
"slug": "bar",
"sqProjectKey": "bar",
},
]
}
selectedRepository={
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
}
}
showingAllRepositories={true}
/>
<BitbucketProjectAccordion
disableRepositories={false}
key="project2"
onClick={[Function]}
onSelectRepository={[MockFunction]}
open={false}
project={
Object {
"id": 2,
"key": "project2",
"name": "Project 2",
}
}
repositories={Array []}
selectedRepository={
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
}
}
/>
</Fragment>
`;

+ 57
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketSearchResults-test.tsx.snap View File

@@ -0,0 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<div
className="big-spacer-top"
>
<DeferredSpinner
loading={false}
timeout={100}
>
<BitbucketProjectAccordion
disableRepositories={false}
key="project"
onSelectRepository={[MockFunction]}
open={true}
project={
Object {
"id": 1,
"key": "project",
"name": "Project",
}
}
repositories={
Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
]
}
showingAllRepositories={true}
/>
</DeferredSpinner>
</div>
`;

exports[`should render correctly: no results 1`] = `
<Alert
className="big-spacer-top"
variant="warning"
>
onboarding.create_project.no_bbs_repos.filter
</Alert>
`;

exports[`should render correctly: searching 1`] = `
<div
className="big-spacer-top"
>
<DeferredSpinner
loading={true}
timeout={100}
/>
</div>
`;

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -3095,6 +3095,7 @@ onboarding.create_project.display_name.description=Up to 255 characters
onboarding.create_project.display_name.help=Some scanners might override the value you provide.
onboarding.create_project.repository_imported=Already set up
onboarding.create_project.see_project=See the project
onboarding.create_project.search_repositories_by_name=Search for repository name starting with...
onboarding.create_project.select_repositories=Select repositories
onboarding.create_project.select_all_repositories=Select all available repositories
onboarding.create_project.subscribe_to_import_private_repositories=You need to subscribe your organization to a paid plan to import private projects
@@ -3125,6 +3126,7 @@ onboarding.create_project.error_fetching_bbs_repos=There was an error fetching t
onboarding.create_project.no_bbs_projects=No projects could be fetched from Bitbucket Server. Contact your system administrator, or check your personal access token.
onboarding.create_project.no_bbs_repos=No repositories were found for this project. Contact your system administrator, or check your personal access token.
onboarding.create_project.no_bbs_repos.filter=No repositories match your filter.
onboarding.create_project.only_showing_X_first_repos=We're only displaying the first {0} repositories. If you're looking for a repository that's not in this list, use the search above.
onboarding.create_project.import_selected_repo=Set up selected repository
onboarding.create_project.go_to_project=Go to project


Loading…
Cancel
Save