Преглед изворни кода

SONAR-13001 Add new option for BBS project import

tags/8.2.0.32929
Wouter Admiraal пре 4 година
родитељ
комит
1fba92e6a8
30 измењених фајлова са 3217 додато и 180 уклоњено
  1. 1
    1
      server/sonar-web/src/main/js/api/almSettings.ts
  2. 10
    0
      server/sonar-web/src/main/js/app/styles/init/misc.css
  3. 131
    0
      server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx
  4. 142
    0
      server/sonar-web/src/main/js/apps/create/project/BitbucketPersonalAccessTokenForm.tsx
  5. 243
    0
      server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx
  6. 139
    0
      server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx
  7. 102
    0
      server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx
  8. 48
    0
      server/sonar-web/src/main/js/apps/create/project/CreateProjectPageHeader.tsx
  9. 102
    17
      server/sonar-web/src/main/js/apps/create/project/CreateProjectPageSonarQube.tsx
  10. 96
    86
      server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx
  11. 84
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketImportRepositoryForm-test.tsx
  12. 64
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketPersonalAccessTokenForm-test.tsx
  13. 139
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreate-test.tsx
  14. 64
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreateRenderer-test.tsx
  15. 56
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx
  16. 33
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPageHeader-test.tsx
  17. 85
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPageSonarQube-test.tsx
  18. 440
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketImportRepositoryForm-test.tsx.snap
  19. 225
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketPersonalAccessTokenForm-test.tsx.snap
  20. 19
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreate-test.tsx.snap
  21. 383
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap
  22. 267
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap
  23. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
  24. 48
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPageHeader-test.tsx.snap
  25. 114
    0
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPageSonarQube-test.tsx.snap
  26. 77
    72
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap
  27. 37
    0
      server/sonar-web/src/main/js/apps/create/project/style.css
  28. 23
    0
      server/sonar-web/src/main/js/apps/create/project/types.ts
  29. 12
    0
      server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts
  30. 32
    3
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 1
- 1
server/sonar-web/src/main/js/api/almSettings.ts Прегледај датотеку

@@ -37,7 +37,7 @@ export function getAlmDefinitions(): Promise<AlmSettingsBindingDefinitions> {
return getJSON('/api/alm_settings/list_definitions').catch(throwGlobalError);
}

export function getAlmSettings(project: string): Promise<AlmSettingsInstance[]> {
export function getAlmSettings(project?: string): Promise<AlmSettingsInstance[]> {
return getJSON('/api/alm_settings/list', { project })
.then(({ almSettings }) => almSettings)
.catch(throwGlobalError);

+ 10
- 0
server/sonar-web/src/main/js/app/styles/init/misc.css Прегледај датотеку

@@ -380,6 +380,11 @@ th.huge-spacer-right {
align-items: flex-start;
}

.display-flex-wrap {
display: flex !important;
flex-wrap: wrap;
}

.display-inline-flex-baseline {
display: inline-flex !important;
align-items: baseline;
@@ -390,6 +395,11 @@ th.huge-spacer-right {
align-items: center;
}

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

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

+ 131
- 0
server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx Прегледај датотеку

@@ -0,0 +1,131 @@
/*
* 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 { 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 { 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';

export interface BitbucketImportRepositoryFormProps {
importing?: boolean;
onSelectRepository: (repo: BitbucketRepository) => void;
projects?: BitbucketProject[];
projectRepositories?: T.Dict<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] : []
);

if (projects.length === 0) {
return (
<Alert 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>
)}

{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>
);
})}
</div>
);
}

+ 142
- 0
server/sonar-web/src/main/js/apps/create/project/BitbucketPersonalAccessTokenForm.tsx Прегледај датотеку

@@ -0,0 +1,142 @@
/*
* 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 { FormattedMessage } from 'react-intl';
import { SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput';
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 { AlmSettingsInstance } from '../../../types/alm-settings';

export interface BitbucketPersonalAccessTokenFormProps {
bitbucketSetting: AlmSettingsInstance;
onPersonalAccessTokenCreate: (token: string) => void;
submitting?: boolean;
}

export default function BitbucketPersonalAccessTokenForm(
props: BitbucketPersonalAccessTokenFormProps
) {
const {
bitbucketSetting: { url },
submitting = false
} = props;
const [personalAccessToken, setPersonalAccessToken] = React.useState('');
const isValid = personalAccessToken.length > 0;

return (
<div className="display-flex-start">
<form
onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
props.onPersonalAccessTokenCreate(personalAccessToken);
}}>
<h2 className="big">{translate('onboarding.create_project.grant_access_to_bbs.title')}</h2>
<p className="big-spacer-top big-spacer-bottom">
{translate('onboarding.create_project.grant_access_to_bbs.help')}
</p>

<ValidationInput
error={undefined}
id="personal_access_token"
isInvalid={false}
isValid={isValid}
label={translate('onboarding.create_project.enter_pat')}
required={true}>
<input
autoFocus={true}
className={classNames('input-super-large', {
'is-valid': isValid
})}
id="personal_access_token"
minLength={1}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setPersonalAccessToken(e.currentTarget.value)
}
type="text"
value={personalAccessToken}
/>
</ValidationInput>

<SubmitButton disabled={!isValid || submitting}>{translate('save')}</SubmitButton>
<DeferredSpinner className="spacer-left" loading={submitting} />
</form>

<Alert className="big-spacer-left big-spacer-top" display="block" variant="info">
<h3>{translate('onboarding.create_project.pat_help.title')}</h3>

<p className="big-spacer-top big-spacer-bottom">
{translate('onboarding.create_project.pat_help.bbs_help_1')}
</p>

{url && (
<div className="text-middle">
<img
alt="" // Should be ignored by screen readers
className="spacer-right"
height="16"
src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
/>
<a
href={`${url.replace(/\/$/, '')}/plugins/servlet/access-tokens/add`}
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.create_project.pat_help.link')}
</a>
</div>
)}

<p className="big-spacer-top big-spacer-bottom">
{translate('onboarding.create_project.pat_help.bbs_help_2')}
</p>

<ul>
<li>
<FormattedMessage
defaultMessage={translate(
'onboarding.create_project.pat_help.bbs_permission_projects'
)}
id="onboarding.create_project.pat_help.bbs_permission_projects"
values={{
perm: (
<strong>{translate('onboarding.create_project.pat_help.read_permission')}</strong>
)
}}
/>
</li>
<li>
<FormattedMessage
defaultMessage={translate('onboarding.create_project.pat_help.bbs_permission_repos')}
id="onboarding.create_project.pat_help.bbs_permission_repos"
values={{
perm: (
<strong>{translate('onboarding.create_project.pat_help.read_permission')}</strong>
)
}}
/>
</li>
</ul>
</Alert>
</div>
);
}

+ 243
- 0
server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx Прегледај датотеку

@@ -0,0 +1,243 @@
/*
* 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 React from 'react';
import { connect } from 'react-redux';
import { WithRouterProps } from 'react-router';
import {
checkPersonalAccessTokenIsValid,
getBitbucketServerProjects,
getBitbucketServerRepositories,
importBitbucketServerProject,
setAlmPersonalAccessToken
} from '../../../api/alm-integrations';
import { getAppState, Store } from '../../../store/rootReducer';
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../types/alm-settings';
import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer';

interface Props extends Pick<WithRouterProps, 'location'> {
bitbucketSettings: AlmSettingsInstance[];
canAdmin?: boolean;
loadingBindings: boolean;
onProjectCreate: (projectKeys: string[]) => void;
}

interface State {
bitbucketSetting?: AlmSettingsInstance;
importing: boolean;
loading: boolean;
patIsValid?: boolean;
projects?: BitbucketProject[];
projectRepositories?: T.Dict<BitbucketRepository[]>;
selectedRepository?: BitbucketRepository;
submittingToken?: boolean;
}

export class BitbucketProjectCreate extends React.PureComponent<Props, State> {
mounted = false;

constructor(props: Props) {
super(props);
this.state = {
// For now, we only handle a single instance. So we always use the first
// one from the list.
bitbucketSetting: props.bitbucketSettings[0],
importing: false,
loading: false
};
}

componentDidMount() {
this.mounted = true;
this.fetchInitialData();
}

componentDidUpdate(prevProps: Props) {
if (prevProps.bitbucketSettings.length === 0 && this.props.bitbucketSettings.length > 0) {
this.setState({ bitbucketSetting: this.props.bitbucketSettings[0] });
this.fetchInitialData();
}
}

componentWillUnmount() {
this.mounted = false;
}

fetchInitialData = async () => {
this.setState({ loading: true });

const patIsValid = await this.checkPersonalAccessToken().catch(() => false);

let projects;
if (patIsValid) {
projects = await this.fetchBitbucketProjects().catch(() => undefined);
}

let projectRepositories;
if (projects && projects.length > 0) {
projectRepositories = await this.fetchBitbucketRepositories(projects).catch(() => undefined);
}

if (this.mounted) {
this.setState({
patIsValid,
projects,
projectRepositories,
loading: false
});
}
};

checkPersonalAccessToken = () => {
const { bitbucketSetting } = this.state;

if (!bitbucketSetting) {
return Promise.resolve(false);
}

return checkPersonalAccessTokenIsValid(bitbucketSetting.key);
};

fetchBitbucketProjects = (): Promise<BitbucketProject[] | undefined> => {
const { bitbucketSetting } = this.state;

if (!bitbucketSetting) {
return Promise.resolve(undefined);
}

return getBitbucketServerProjects(bitbucketSetting.key).then(({ projects }) => projects);
};

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

if (!bitbucketSetting) {
return Promise.resolve(undefined);
}

return Promise.all(
projects.map(p => {
return getBitbucketServerRepositories(bitbucketSetting.key, p.name).then(
({ repositories }) => ({
repositories,
projectKey: p.key
})
);
})
).then(results => {
return results.reduce((acc: T.Dict<BitbucketRepository[]>, { projectKey, repositories }) => {
return { ...acc, [projectKey]: repositories };
}, {});
});
};

handlePersonalAccessTokenCreate = (token: string) => {
const { bitbucketSetting } = this.state;

if (!bitbucketSetting || token.length < 1) {
return;
}

this.setState({ submittingToken: true });
setAlmPersonalAccessToken(bitbucketSetting.key, token)
.then(() => {
if (this.mounted) {
this.setState({ submittingToken: false });
this.fetchInitialData();
}
})
.catch(() => {
if (this.mounted) {
this.setState({ submittingToken: false });
}
});
};

handleImportRepository = () => {
const { bitbucketSetting, selectedRepository } = this.state;

if (!bitbucketSetting || !selectedRepository) {
return;
}

this.setState({ importing: true });
importBitbucketServerProject(
bitbucketSetting.key,
selectedRepository.projectKey,
selectedRepository.slug
)
.then(({ project: { key } }) => {
if (this.mounted) {
this.setState({ importing: false });
this.props.onProjectCreate([key]);
}
})
.catch(() => {
if (this.mounted) {
this.setState({ importing: false });
}
});
};

handleSelectRepository = (selectedRepository: BitbucketRepository) => {
this.setState({ selectedRepository });
};

render() {
const { canAdmin, loadingBindings } = this.props;
const {
bitbucketSetting,
importing,
loading,
patIsValid,
projectRepositories,
projects,
selectedRepository,
submittingToken
} = this.state;

return (
<BitbucketCreateProjectRenderer
bitbucketSetting={bitbucketSetting}
canAdmin={canAdmin}
importing={importing}
loading={loading || loadingBindings}
onImportRepository={this.handleImportRepository}
onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate}
onProjectCreate={this.props.onProjectCreate}
onSelectRepository={this.handleSelectRepository}
projectRepositories={projectRepositories}
projects={projects}
selectedRepository={selectedRepository}
showPersonalAccessTokenForm={!patIsValid}
submittingToken={submittingToken}
/>
);
}
}

const mapStateToProps = (state: Store) => {
const { canAdmin } = getAppState(state);
return { canAdmin };
};

export default connect(mapStateToProps)(BitbucketProjectCreate);

+ 139
- 0
server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx Прегледај датотеку

@@ -0,0 +1,139 @@
/*
* 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 React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import { Button } from 'sonar-ui-common/components/controls/buttons';
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 { AlmSettingsInstance } from '../../../types/alm-settings';
import { PULL_REQUEST_DECORATION_BINDING_CATEGORY } from '../../settings/components/AdditionalCategoryKeys';
import BitbucketImportRepositoryForm from './BitbucketImportRepositoryForm';
import BitbucketPersonalAccessTokenForm from './BitbucketPersonalAccessTokenForm';
import CreateProjectPageHeader from './CreateProjectPageHeader';

export interface BitbucketProjectCreateRendererProps {
bitbucketSetting?: AlmSettingsInstance;
canAdmin?: boolean;
importing: boolean;
loading: boolean;
onImportRepository: () => void;
onSelectRepository: (repo: BitbucketRepository) => void;
onPersonalAccessTokenCreate: (token: string) => void;
onProjectCreate: (projectKeys: string[]) => void;
projects?: BitbucketProject[];
projectRepositories?: T.Dict<BitbucketRepository[]>;
selectedRepository?: BitbucketRepository;
showPersonalAccessTokenForm?: boolean;
submittingToken?: boolean;
}

export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCreateRendererProps) {
const {
bitbucketSetting,
canAdmin,
importing,
loading,
projects,
projectRepositories,
selectedRepository,
showPersonalAccessTokenForm,
submittingToken
} = props;

return (
<>
<CreateProjectPageHeader
additionalActions={
!showPersonalAccessTokenForm && (
<div className="display-flex-center pull-right">
<DeferredSpinner className="spacer-right" loading={importing} />
<Button
className="button-large button-primary"
disabled={!selectedRepository || importing}
onClick={props.onImportRepository}>
{translate('onboarding.create_project.import_selected_repo')}
</Button>
</div>
)
}
showBreadcrumb={true}
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>
}
/>

{loading && <i className="spinner" />}

{!loading && !bitbucketSetting && (
<Alert variant="error">
{canAdmin ? (
<FormattedMessage
defaultMessage={translate('onboarding.create_project.no_bbs_binding.admin')}
id="onboarding.create_project.no_bbs_binding.admin"
values={{
url: (
<Link
to={{
pathname: '/admin/settings',
query: { category: PULL_REQUEST_DECORATION_BINDING_CATEGORY }
}}>
{translate('settings.page')}
</Link>
)
}}
/>
) : (
translate('onboarding.create_project.no_bbs_binding')
)}
</Alert>
)}

{!loading &&
bitbucketSetting &&
(showPersonalAccessTokenForm ? (
<BitbucketPersonalAccessTokenForm
bitbucketSetting={bitbucketSetting}
onPersonalAccessTokenCreate={props.onPersonalAccessTokenCreate}
submitting={submittingToken}
/>
) : (
<BitbucketImportRepositoryForm
importing={importing}
onSelectRepository={props.onSelectRepository}
projectRepositories={projectRepositories}
projects={projects}
selectedRepository={selectedRepository}
/>
))}
</>
);
}

+ 102
- 0
server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx Прегледај датотеку

@@ -0,0 +1,102 @@
/*
* 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 React from 'react';
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
import { CreateProjectModes } from './types';

export interface CreateProjectModeSelectionProps {
bbsBindingCount: number;
loadingBindings: boolean;
onSelectMode: (mode: CreateProjectModes) => void;
}

export default function CreateProjectModeSelection(props: CreateProjectModeSelectionProps) {
const { bbsBindingCount, loadingBindings } = props;

return (
<>
<header className="huge-spacer-top big-spacer-bottom padded">
<h1 className="text-center huge big-spacer-bottom">
{translate('my_account.create_new.TRK')}
</h1>
<p className="text-center big">{translate('onboarding.create_project.select_method')}</p>
</header>

<div className="create-project-modes huge-spacer-top display-flex-space-around">
<button
className="button button-huge display-flex-column create-project-mode-type-manual"
onClick={() => props.onSelectMode(CreateProjectModes.Manual)}
type="button">
<img
alt="" // Should be ignored by screen readers
height={80}
src={`${getBaseUrl()}/images/sonarcloud/analysis/manual.svg`}
width={80}
/>
<div className="medium big-spacer-top">
{translate('onboarding.create_project.select_method.manual')}
</div>
</button>

<button
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs"
disabled={bbsBindingCount !== 1}
onClick={() => props.onSelectMode(CreateProjectModes.BitbucketServer)}
type="button">
<img
alt="" // Should be ignored by screen readers
height={80}
src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
width={80}
/>
<div className="medium big-spacer-top">
{translate('onboarding.create_project.select_method.from_bbs')}
</div>

{loadingBindings && (
<span>
{translate('onboarding.create_project.check_bbs_supported')}
<i className="little-spacer-left spinner" />
</span>
)}

{!loadingBindings && bbsBindingCount !== 1 && (
<div className="text-muted small spacer-top" style={{ lineHeight: 1.5 }}>
{translate('onboarding.create_project.bbs_not_configured')}
<HelpTooltip
className="little-spacer-left"
overlay={
bbsBindingCount === 0
? translate('onboarding.create_project.zero_bbs_instances')
: translateWithParameters(
'onboarding.create_project.too_many_bbs_instances_X',
bbsBindingCount
)
}
/>
</div>
)}
</button>
</div>
</>
);
}

+ 48
- 0
server/sonar-web/src/main/js/apps/create/project/CreateProjectPageHeader.tsx Прегледај датотеку

@@ -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 * as React from 'react';
import { Link } from 'react-router';
import { translate } from 'sonar-ui-common/helpers/l10n';

export interface CreateProjectPageHeaderProps {
additionalActions?: React.ReactNode;
showBreadcrumb?: boolean;
title: React.ReactNode;
}

export default function CreateProjectPageHeader(props: CreateProjectPageHeaderProps) {
const { additionalActions, showBreadcrumb, title } = props;

return (
<header className="huge-spacer-bottom bordered-bottom overflow-hidden">
<h1 className="pull-left huge big-spacer-bottom">
{showBreadcrumb && (
<>
<Link to="/projects/create">{translate('my_account.create_new.TRK')}</Link>
<span className="big-spacer-left big-spacer-right slash-separator" />
</>
)}
{title}
</h1>

{additionalActions}
</header>
);
}

+ 102
- 17
server/sonar-web/src/main/js/apps/create/project/CreateProjectPageSonarQube.tsx Прегледај датотеку

@@ -22,50 +22,135 @@ import { Helmet } from 'react-helmet-async';
import { WithRouterProps } from 'react-router';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { addWhitePageClass, removeWhitePageClass } from 'sonar-ui-common/helpers/pages';
import { getAlmSettings } from '../../../api/almSettings';
import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn';
import { withAppState } from '../../../components/hoc/withAppState';
import { getProjectUrl } from '../../../helpers/urls';
import { AlmSettingsInstance, ALM_KEYS } from '../../../types/alm-settings';
import BitbucketProjectCreate from './BitbucketProjectCreate';
import CreateProjectModeSelection from './CreateProjectModeSelection';
import ManualProjectCreate from './ManualProjectCreate';
import './style.css';
import { CreateProjectModes } from './types';

interface Props {
interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
appState: Pick<T.AppState, 'branchesEnabled'>;
currentUser: T.LoggedInUser;
}

export class CreateProjectPageSonarQube extends React.PureComponent<Props & WithRouterProps> {
interface State {
bitbucketSettings: AlmSettingsInstance[];
loading: boolean;
}

export class CreateProjectPageSonarQube extends React.PureComponent<Props, State> {
mounted = false;
state: State = { bitbucketSettings: [], loading: false };

componentDidMount() {
addWhitePageClass();
const {
appState: { branchesEnabled },
location
} = this.props;
this.mounted = true;
if (branchesEnabled) {
this.fetchAlmBindings();
}

if (location.query?.mode || !branchesEnabled) {
addWhitePageClass();
}
}

componentDidUpdate(prevProps: Props) {
if (this.props.location.query?.mode && !prevProps.location.query?.mode) {
addWhitePageClass();
} else if (!this.props.location.query?.mode && prevProps.location.query?.mode) {
removeWhitePageClass();
}
}

componentWillUnmount() {
this.mounted = false;
removeWhitePageClass();
}

fetchAlmBindings = () => {
this.setState({ loading: true });
getAlmSettings()
.then(almSettings => {
if (this.mounted) {
this.setState({
bitbucketSettings: almSettings.filter(s => s.alm === ALM_KEYS.BITBUCKET),
loading: false
});
}
})
.catch(() => {
if (this.mounted) {
this.setState({ loading: false });
}
});
};

handleProjectCreate = (projectKeys: string[]) => {
if (projectKeys.length === 1) {
this.props.router.push(getProjectUrl(projectKeys[0]));
}
};

handleModeSelect = (mode: CreateProjectModes) => {
const { router, location } = this.props;
router.push({
pathname: location.pathname,
query: { mode }
});
};

render() {
const { currentUser } = this.props;
const header = translate('my_account.create_new.TRK');
const {
appState: { branchesEnabled },
currentUser,
location
} = this.props;
const { bitbucketSettings, loading } = this.state;

const mode: CreateProjectModes | undefined = location.query?.mode;
const showManualForm = !branchesEnabled || mode === CreateProjectModes.Manual;
const showBBSForm = branchesEnabled && mode === CreateProjectModes.BitbucketServer;

return (
<>
<Helmet title={header} titleTemplate="%s" />
<div className="page page-limited huge-spacer-top huge-spacer-bottom">
<header className="page-header bordered-bottom big-spacer-bottom">
<h1 className="page-title huge big-spacer-bottom">
<strong>{header}</strong>
</h1>
</header>
<ManualProjectCreate
currentUser={currentUser}
onProjectCreate={this.handleProjectCreate}
/>
<Helmet title={translate('my_account.create_new.TRK')} titleTemplate="%s" />
<div className="page page-limited huge-spacer-bottom position-relative" id="create-project">
{!showBBSForm && !showManualForm && (
<CreateProjectModeSelection
bbsBindingCount={bitbucketSettings.length}
loadingBindings={loading}
onSelectMode={this.handleModeSelect}
/>
)}

{showManualForm && (
<ManualProjectCreate
branchesEnabled={branchesEnabled}
currentUser={currentUser}
onProjectCreate={this.handleProjectCreate}
/>
)}

{showBBSForm && (
<BitbucketProjectCreate
bitbucketSettings={bitbucketSettings}
loadingBindings={loading}
location={location}
onProjectCreate={this.handleProjectCreate}
/>
)}
</div>
</>
);
}
}

export default whenLoggedIn(CreateProjectPageSonarQube);
export default whenLoggedIn(withAppState(CreateProjectPageSonarQube));

+ 96
- 86
server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx Прегледај датотеку

@@ -28,10 +28,12 @@ import { createProject, doesComponentExists } from '../../../api/components';
import VisibilitySelector from '../../../components/common/VisibilitySelector';
import { isSonarCloud } from '../../../helpers/system';
import UpgradeOrganizationBox from '../components/UpgradeOrganizationBox';
import CreateProjectPageHeader from './CreateProjectPageHeader';
import './ManualProjectCreate.css';
import OrganizationInput from './OrganizationInput';

interface Props {
branchesEnabled?: boolean;
currentUser: T.LoggedInUser;
fetchMyOrganizations?: () => Promise<void>;
onProjectCreate: (projectKeys: string[]) => void;
@@ -248,6 +250,7 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
touched,
validating
} = this.state;
const { branchesEnabled } = this.props;
const projectKeyIsInvalid = touched && projectKeyError !== undefined;
const projectKeyIsValid = touched && !validating && projectKeyError === undefined;
const projectNameIsInvalid = touched && projectNameError !== undefined;
@@ -255,97 +258,104 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
const canChoosePrivate = this.canChoosePrivate(selectedOrganization);

return (
<div className="create-project">
<div className="flex-1 huge-spacer-right">
<form className="manual-project-create" onSubmit={this.handleFormSubmit}>
{isSonarCloud() && this.props.userOrganizations && (
<OrganizationInput
onChange={this.handleOrganizationSelect}
organization={selectedOrganization ? selectedOrganization.key : ''}
organizations={this.props.userOrganizations}
/>
)}

<ValidationInput
className="form-field"
description={translate('onboarding.create_project.project_key.description')}
error={projectKeyError}
help={translate('onboarding.create_project.project_key.help')}
id="project-key"
isInvalid={projectKeyIsInvalid}
isValid={projectKeyIsValid}
label={translate('onboarding.create_project.project_key')}
required={true}>
<input
autoFocus={true}
className={classNames('input-super-large', {
'is-invalid': projectKeyIsInvalid,
'is-valid': projectKeyIsValid
})}
<>
<CreateProjectPageHeader
showBreadcrumb={branchesEnabled}
title={translate('onboarding.create_project.setup_manually')}
/>

<div className="create-project">
<div className="flex-1 huge-spacer-right">
<form className="manual-project-create" onSubmit={this.handleFormSubmit}>
{isSonarCloud() && this.props.userOrganizations && (
<OrganizationInput
onChange={this.handleOrganizationSelect}
organization={selectedOrganization ? selectedOrganization.key : ''}
organizations={this.props.userOrganizations}
/>
)}

<ValidationInput
className="form-field"
description={translate('onboarding.create_project.project_key.description')}
error={projectKeyError}
help={translate('onboarding.create_project.project_key.help')}
id="project-key"
maxLength={400}
minLength={1}
onChange={this.handleProjectKeyChange}
type="text"
value={projectKey}
/>
</ValidationInput>

<ValidationInput
className="form-field"
description={translate('onboarding.create_project.display_name.description')}
error={projectNameError}
help={translate('onboarding.create_project.display_name.help')}
id="project-name"
isInvalid={projectNameIsInvalid}
isValid={projectNameIsValid}
label={translate('onboarding.create_project.display_name')}
required={true}>
<input
className={classNames('input-super-large', {
'is-invalid': projectNameIsInvalid,
'is-valid': projectNameIsValid
})}
isInvalid={projectKeyIsInvalid}
isValid={projectKeyIsValid}
label={translate('onboarding.create_project.project_key')}
required={true}>
<input
autoFocus={true}
className={classNames('input-super-large', {
'is-invalid': projectKeyIsInvalid,
'is-valid': projectKeyIsValid
})}
id="project-key"
maxLength={400}
minLength={1}
onChange={this.handleProjectKeyChange}
type="text"
value={projectKey}
/>
</ValidationInput>

<ValidationInput
className="form-field"
description={translate('onboarding.create_project.display_name.description')}
error={projectNameError}
help={translate('onboarding.create_project.display_name.help')}
id="project-name"
maxLength={255}
minLength={1}
onChange={this.handleProjectNameChange}
type="text"
value={projectName}
/>
</ValidationInput>

{isSonarCloud() && selectedOrganization && (
<div
className={classNames('visibility-select-wrapper', {
open: Boolean(this.state.selectedOrganization)
})}>
<VisibilitySelector
canTurnToPrivate={canChoosePrivate}
onChange={this.handleVisibilityChange}
showDetails={true}
visibility={canChoosePrivate ? this.state.selectedVisibility : 'public'}
isInvalid={projectNameIsInvalid}
isValid={projectNameIsValid}
label={translate('onboarding.create_project.display_name')}
required={true}>
<input
className={classNames('input-super-large', {
'is-invalid': projectNameIsInvalid,
'is-valid': projectNameIsValid
})}
id="project-name"
maxLength={255}
minLength={1}
onChange={this.handleProjectNameChange}
type="text"
value={projectName}
/>
</div>
)}

<SubmitButton disabled={!this.canSubmit(this.state) || submitting}>
{translate('set_up')}
</SubmitButton>
<DeferredSpinner className="spacer-left" loading={submitting} />
</form>
</div>
</ValidationInput>

{isSonarCloud() && selectedOrganization && (
<div
className={classNames('visibility-select-wrapper', {
open: Boolean(this.state.selectedOrganization)
})}>
<VisibilitySelector
canTurnToPrivate={canChoosePrivate}
onChange={this.handleVisibilityChange}
showDetails={true}
visibility={canChoosePrivate ? this.state.selectedVisibility : 'public'}
/>
</div>
)}

{isSonarCloud() && selectedOrganization && (
<div className="create-project-side-sticky">
<UpgradeOrganizationBox
className={classNames('animated', { open: !canChoosePrivate })}
onOrganizationUpgrade={this.handleOrganizationUpgrade}
organization={selectedOrganization}
/>
<SubmitButton disabled={!this.canSubmit(this.state) || submitting}>
{translate('set_up')}
</SubmitButton>
<DeferredSpinner className="spacer-left" loading={submitting} />
</form>
</div>
)}
</div>

{isSonarCloud() && selectedOrganization && (
<div className="create-project-side-sticky">
<UpgradeOrganizationBox
className={classNames('animated', { open: !canChoosePrivate })}
onOrganizationUpgrade={this.handleOrganizationUpgrade}
organization={selectedOrganization}
/>
</div>
)}
</div>
</>
);
}
}

+ 84
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketImportRepositoryForm-test.tsx Прегледај датотеку

@@ -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.
*/
/* 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 {
mockBitbucketProject,
mockBitbucketRepository
} from '../../../../helpers/mocks/alm-integrations';
import BitbucketImportRepositoryForm, {
BitbucketImportRepositoryFormProps
} from '../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'
);
});

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);
});

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

+ 64
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketPersonalAccessTokenForm-test.tsx Прегледај датотеку

@@ -0,0 +1,64 @@
/*
* 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 { SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import { change, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { ALM_KEYS } from '../../../../types/alm-settings';
import BitbucketPersonalAccessTokenForm, {
BitbucketPersonalAccessTokenFormProps
} from '../BitbucketPersonalAccessTokenForm';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting');
});

it('should correctly handle form interactions', async () => {
const onPersonalAccessTokenCreate = jest.fn();
const wrapper = shallowRender({ onPersonalAccessTokenCreate });

// Submit button disabled by default.
expect(wrapper.find(SubmitButton).prop('disabled')).toBe(true);

// Submit button enabled if there's a value.
change(wrapper.find('input'), 'token');
expect(wrapper.find(SubmitButton).prop('disabled')).toBe(false);

// Expect correct calls to be made when submitting.
submit(wrapper.find('form'));
await waitAndUpdate(wrapper);
expect(onPersonalAccessTokenCreate).toBeCalled();
});

function shallowRender(props: Partial<BitbucketPersonalAccessTokenFormProps> = {}) {
return shallow<BitbucketPersonalAccessTokenFormProps>(
<BitbucketPersonalAccessTokenForm
bitbucketSetting={mockAlmSettingsInstance({
alm: ALM_KEYS.BITBUCKET,
url: 'http://www.example.com'
})}
onPersonalAccessTokenCreate={jest.fn()}
{...props}
/>
);
}

+ 139
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreate-test.tsx Прегледај датотеку

@@ -0,0 +1,139 @@
/*
* 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 { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import {
checkPersonalAccessTokenIsValid,
getBitbucketServerProjects,
getBitbucketServerRepositories,
importBitbucketServerProject,
setAlmPersonalAccessToken
} from '../../../../api/alm-integrations';
import { mockBitbucketRepository } from '../../../../helpers/mocks/alm-integrations';
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { mockLocation } from '../../../../helpers/testMocks';
import { ALM_KEYS } from '../../../../types/alm-settings';
import { BitbucketProjectCreate } from '../BitbucketProjectCreate';

jest.mock('../../../../api/alm-integrations', () => {
const { mockBitbucketProject, mockBitbucketRepository } = jest.requireActual(
'../../../../helpers/mocks/alm-integrations'
);
return {
checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue(true),
getBitbucketServerProjects: jest.fn().mockResolvedValue({
projects: [
mockBitbucketProject({ key: 'project1', name: 'Project 1' }),
mockBitbucketProject({ id: 2, key: 'project2' })
]
}),
getBitbucketServerRepositories: jest.fn().mockResolvedValue({
repositories: [
mockBitbucketRepository(),
mockBitbucketRepository({ id: 2, slug: 'project__repo2' })
]
}),
importBitbucketServerProject: jest.fn().mockResolvedValue({ project: { key: 'baz' } }),
setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null)
};
});

beforeEach(jest.clearAllMocks);

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should correctly fetch binding info on mount', async () => {
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(checkPersonalAccessTokenIsValid).toBeCalledWith('foo');
});

it('should correctly handle a valid PAT', async () => {
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(true);
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(checkPersonalAccessTokenIsValid).toBeCalled();
expect(wrapper.state().patIsValid).toBe(true);
});

it('should correctly handle an invalid PAT', async () => {
(checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(false);
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(checkPersonalAccessTokenIsValid).toBeCalled();
expect(wrapper.state().patIsValid).toBe(false);
});

it('should correctly handle setting a new PAT', () => {
const wrapper = shallowRender();
wrapper.instance().handlePersonalAccessTokenCreate('token');
expect(setAlmPersonalAccessToken).toBeCalledWith('foo', 'token');
});

it('should correctly fetch projects and repos', async () => {
const wrapper = shallowRender();

// Opens first project on mount.
await waitAndUpdate(wrapper);
expect(getBitbucketServerProjects).toBeCalledWith('foo');
expect(wrapper.state().projects).toHaveLength(2);

// Check repos got loaded.
await waitAndUpdate(wrapper);
expect(getBitbucketServerRepositories).toBeCalledWith('foo', 'Project 1');
expect(wrapper.state().projectRepositories).toEqual(
expect.objectContaining({
project1: expect.arrayContaining([
expect.objectContaining({ id: 1 }),
expect.objectContaining({ id: 2 })
])
})
);
expect(wrapper.state().projectRepositories).toBeDefined();
});

it('should correctly import a repo', async () => {
const onProjectCreate = jest.fn();
const repo = mockBitbucketRepository();
const wrapper = shallowRender({ onProjectCreate });
const instance = wrapper.instance();

instance.handleSelectRepository(repo);
instance.handleImportRepository();
expect(importBitbucketServerProject).toBeCalledWith('foo', repo.projectKey, repo.slug);
await waitAndUpdate(wrapper);
expect(onProjectCreate).toBeCalledWith(['baz']);
});

function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) {
return shallow<BitbucketProjectCreate>(
<BitbucketProjectCreate
bitbucketSettings={[mockAlmSettingsInstance({ alm: ALM_KEYS.BITBUCKET, key: 'foo' })]}
loadingBindings={false}
location={mockLocation()}
onProjectCreate={jest.fn()}
{...props}
/>
);
}

+ 64
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreateRenderer-test.tsx Прегледај датотеку

@@ -0,0 +1,64 @@
/*
* 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 { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { ALM_KEYS } from '../../../../types/alm-settings';
import BitbucketProjectCreateRenderer, {
BitbucketProjectCreateRendererProps
} from '../BitbucketProjectCreateRenderer';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ showPersonalAccessTokenForm: true })).toMatchSnapshot('pat form');
expect(shallowRender({ loading: true })).toMatchSnapshot('loading');
expect(shallowRender({ importing: true })).toMatchSnapshot('importing');
expect(shallowRender({ selectedRepository: mockBitbucketRepository() })).toMatchSnapshot(
'selected repo'
);
expect(shallowRender({ bitbucketSetting: undefined })).toMatchSnapshot(
'invalid config, regular user'
);
expect(shallowRender({ bitbucketSetting: undefined, canAdmin: true })).toMatchSnapshot(
'invalid config, admin user'
);
});

function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {}) {
return shallow<BitbucketProjectCreateRendererProps>(
<BitbucketProjectCreateRenderer
bitbucketSetting={mockAlmSettingsInstance({ alm: ALM_KEYS.BITBUCKET })}
importing={false}
loading={false}
onImportRepository={jest.fn()}
onPersonalAccessTokenCreate={jest.fn()}
onProjectCreate={jest.fn()}
onSelectRepository={jest.fn()}
projectRepositories={{ foo: [mockBitbucketRepository()] }}
projects={[mockBitbucketProject({ key: 'foo' })]}
{...props}
/>
);
}

+ 56
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx Прегледај датотеку

@@ -0,0 +1,56 @@
/*
* 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 { click } from 'sonar-ui-common/helpers/testUtils';
import CreateProjectModeSelection, {
CreateProjectModeSelectionProps
} from '../CreateProjectModeSelection';
import { CreateProjectModes } from '../types';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ loadingBindings: true })).toMatchSnapshot('loading bbs instances');
expect(shallowRender({ bbsBindingCount: 0 })).toMatchSnapshot('no bbs instances');
expect(shallowRender({ bbsBindingCount: 2 })).toMatchSnapshot('too many bbs instances');
});

it('should correctly pass the selected mode up', () => {
const onSelectMode = jest.fn();
const wrapper = shallowRender({ onSelectMode });

click(wrapper.find('button.create-project-mode-type-manual'));
expect(onSelectMode).toBeCalledWith(CreateProjectModes.Manual);

click(wrapper.find('button.create-project-mode-type-bbs'));
expect(onSelectMode).toBeCalledWith(CreateProjectModes.BitbucketServer);
});

function shallowRender(props: Partial<CreateProjectModeSelectionProps> = {}) {
return shallow<CreateProjectModeSelectionProps>(
<CreateProjectModeSelection
bbsBindingCount={1}
loadingBindings={false}
onSelectMode={jest.fn()}
{...props}
/>
);
}

+ 33
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPageHeader-test.tsx Прегледај датотеку

@@ -0,0 +1,33 @@
/*
* 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 CreateProjectPageHeader, { CreateProjectPageHeaderProps } from '../CreateProjectPageHeader';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ showBreadcrumb: true })).toMatchSnapshot('with breadcrumb');
expect(shallowRender({ additionalActions: 'Bar' })).toMatchSnapshot('additional content');
});

function shallowRender(props: Partial<CreateProjectPageHeaderProps> = {}) {
return shallow<CreateProjectPageHeaderProps>(<CreateProjectPageHeader title="Foo" {...props} />);
}

+ 85
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPageSonarQube-test.tsx Прегледај датотеку

@@ -0,0 +1,85 @@
/*
* 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 { addWhitePageClass } from 'sonar-ui-common/helpers/pages';
import { getAlmSettings } from '../../../../api/almSettings';
import { mockLocation, mockLoggedInUser, mockRouter } from '../../../../helpers/testMocks';
import { ALM_KEYS } from '../../../../types/alm-settings';
import { CreateProjectPageSonarQube } from '../CreateProjectPageSonarQube';
import { CreateProjectModes } from '../types';

jest.mock('../../../../api/almSettings', () => ({
getAlmSettings: jest.fn().mockResolvedValue([{ alm: ALM_KEYS.BITBUCKET, key: 'foo' }])
}));

jest.mock('sonar-ui-common/helpers/pages', () => ({
addWhitePageClass: jest.fn(),
removeWhitePageClass: jest.fn()
}));

beforeEach(jest.clearAllMocks);

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(getAlmSettings).toBeCalled();
});

it('should render correctly if no branch support', () => {
expect(shallowRender({ appState: { branchesEnabled: false } })).toMatchSnapshot();
expect(getAlmSettings).not.toBeCalled();
});

it('should render correctly if the manual method is selected', () => {
const push = jest.fn();
const location = { query: { mode: CreateProjectModes.Manual } };
const wrapper = shallowRender({ router: mockRouter({ push }) });

wrapper.instance().handleModeSelect(CreateProjectModes.Manual);
expect(push).toBeCalledWith(expect.objectContaining(location));

expect(wrapper.setProps({ location: mockLocation(location) })).toMatchSnapshot();
expect(addWhitePageClass).toBeCalled();
});

it('should render correctly if the BBS method is selected', () => {
const push = jest.fn();
const location = { query: { mode: CreateProjectModes.BitbucketServer } };
const wrapper = shallowRender({ router: mockRouter({ push }) });

wrapper.instance().handleModeSelect(CreateProjectModes.BitbucketServer);
expect(push).toBeCalledWith(expect.objectContaining(location));

expect(wrapper.setProps({ location: mockLocation(location) })).toMatchSnapshot();
expect(addWhitePageClass).toBeCalled();
});

function shallowRender(props: Partial<CreateProjectPageSonarQube['props']> = {}) {
return shallow<CreateProjectPageSonarQube>(
<CreateProjectPageSonarQube
appState={{ branchesEnabled: true }}
currentUser={mockLoggedInUser()}
location={mockLocation()}
router={mockRouter()}
{...props}
/>
);
}

+ 440
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketImportRepositoryForm-test.tsx.snap Прегледај датотеку

@@ -0,0 +1,440 @@
// 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>
}
/>
</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>
}
>
<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>
}
/>
</div>
`;

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

exports[`should render correctly: no repos 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"
>
<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>
}
/>
</div>
`;

exports[`should render correctly: selected repo 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>
}
/>
</div>
`;

+ 225
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketPersonalAccessTokenForm-test.tsx.snap Прегледај датотеку

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

exports[`should render correctly: default 1`] = `
<div
className="display-flex-start"
>
<form
onSubmit={[Function]}
>
<h2
className="big"
>
onboarding.create_project.grant_access_to_bbs.title
</h2>
<p
className="big-spacer-top big-spacer-bottom"
>
onboarding.create_project.grant_access_to_bbs.help
</p>
<ValidationInput
id="personal_access_token"
isInvalid={false}
isValid={false}
label="onboarding.create_project.enter_pat"
required={true}
>
<input
autoFocus={true}
className="input-super-large"
id="personal_access_token"
minLength={1}
onChange={[Function]}
type="text"
value=""
/>
</ValidationInput>
<SubmitButton
disabled={true}
>
save
</SubmitButton>
<DeferredSpinner
className="spacer-left"
loading={false}
timeout={100}
/>
</form>
<Alert
className="big-spacer-left big-spacer-top"
display="block"
variant="info"
>
<h3>
onboarding.create_project.pat_help.title
</h3>
<p
className="big-spacer-top big-spacer-bottom"
>
onboarding.create_project.pat_help.bbs_help_1
</p>
<div
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="16"
src="/images/alm/bitbucket.svg"
/>
<a
href="http://www.example.com/plugins/servlet/access-tokens/add"
rel="noopener noreferrer"
target="_blank"
>
onboarding.create_project.pat_help.link
</a>
</div>
<p
className="big-spacer-top big-spacer-bottom"
>
onboarding.create_project.pat_help.bbs_help_2
</p>
<ul>
<li>
<FormattedMessage
defaultMessage="onboarding.create_project.pat_help.bbs_permission_projects"
id="onboarding.create_project.pat_help.bbs_permission_projects"
values={
Object {
"perm": <strong>
onboarding.create_project.pat_help.read_permission
</strong>,
}
}
/>
</li>
<li>
<FormattedMessage
defaultMessage="onboarding.create_project.pat_help.bbs_permission_repos"
id="onboarding.create_project.pat_help.bbs_permission_repos"
values={
Object {
"perm": <strong>
onboarding.create_project.pat_help.read_permission
</strong>,
}
}
/>
</li>
</ul>
</Alert>
</div>
`;

exports[`should render correctly: submitting 1`] = `
<div
className="display-flex-start"
>
<form
onSubmit={[Function]}
>
<h2
className="big"
>
onboarding.create_project.grant_access_to_bbs.title
</h2>
<p
className="big-spacer-top big-spacer-bottom"
>
onboarding.create_project.grant_access_to_bbs.help
</p>
<ValidationInput
id="personal_access_token"
isInvalid={false}
isValid={false}
label="onboarding.create_project.enter_pat"
required={true}
>
<input
autoFocus={true}
className="input-super-large"
id="personal_access_token"
minLength={1}
onChange={[Function]}
type="text"
value=""
/>
</ValidationInput>
<SubmitButton
disabled={true}
>
save
</SubmitButton>
<DeferredSpinner
className="spacer-left"
loading={true}
timeout={100}
/>
</form>
<Alert
className="big-spacer-left big-spacer-top"
display="block"
variant="info"
>
<h3>
onboarding.create_project.pat_help.title
</h3>
<p
className="big-spacer-top big-spacer-bottom"
>
onboarding.create_project.pat_help.bbs_help_1
</p>
<div
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="16"
src="/images/alm/bitbucket.svg"
/>
<a
href="http://www.example.com/plugins/servlet/access-tokens/add"
rel="noopener noreferrer"
target="_blank"
>
onboarding.create_project.pat_help.link
</a>
</div>
<p
className="big-spacer-top big-spacer-bottom"
>
onboarding.create_project.pat_help.bbs_help_2
</p>
<ul>
<li>
<FormattedMessage
defaultMessage="onboarding.create_project.pat_help.bbs_permission_projects"
id="onboarding.create_project.pat_help.bbs_permission_projects"
values={
Object {
"perm": <strong>
onboarding.create_project.pat_help.read_permission
</strong>,
}
}
/>
</li>
<li>
<FormattedMessage
defaultMessage="onboarding.create_project.pat_help.bbs_permission_repos"
id="onboarding.create_project.pat_help.bbs_permission_repos"
values={
Object {
"perm": <strong>
onboarding.create_project.pat_help.read_permission
</strong>,
}
}
/>
</li>
</ul>
</Alert>
</div>
`;

+ 19
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreate-test.tsx.snap Прегледај датотеку

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

exports[`should render correctly 1`] = `
<BitbucketProjectCreateRenderer
bitbucketSetting={
Object {
"alm": "bitbucket",
"key": "foo",
}
}
importing={false}
loading={true}
onImportRepository={[Function]}
onPersonalAccessTokenCreate={[Function]}
onProjectCreate={[MockFunction]}
onSelectRepository={[Function]}
showPersonalAccessTokenForm={true}
/>
`;

+ 383
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap Прегледај датотеку

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

exports[`should render correctly: default 1`] = `
<Fragment>
<CreateProjectPageHeader
additionalActions={
<div
className="display-flex-center pull-right"
>
<DeferredSpinner
className="spacer-right"
loading={false}
timeout={100}
/>
<Button
className="button-large button-primary"
disabled={true}
onClick={[MockFunction]}
>
onboarding.create_project.import_selected_repo
</Button>
</div>
}
showBreadcrumb={true}
title={
<span
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="24"
src="/images/alm/bitbucket.svg"
/>
onboarding.create_project.from_bbs
</span>
}
/>
<BitbucketImportRepositoryForm
importing={false}
onSelectRepository={[MockFunction]}
projectRepositories={
Object {
"foo": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
],
}
}
projects={
Array [
Object {
"id": 1,
"key": "foo",
"name": "Project",
},
]
}
/>
</Fragment>
`;

exports[`should render correctly: importing 1`] = `
<Fragment>
<CreateProjectPageHeader
additionalActions={
<div
className="display-flex-center pull-right"
>
<DeferredSpinner
className="spacer-right"
loading={true}
timeout={100}
/>
<Button
className="button-large button-primary"
disabled={true}
onClick={[MockFunction]}
>
onboarding.create_project.import_selected_repo
</Button>
</div>
}
showBreadcrumb={true}
title={
<span
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="24"
src="/images/alm/bitbucket.svg"
/>
onboarding.create_project.from_bbs
</span>
}
/>
<BitbucketImportRepositoryForm
importing={true}
onSelectRepository={[MockFunction]}
projectRepositories={
Object {
"foo": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
],
}
}
projects={
Array [
Object {
"id": 1,
"key": "foo",
"name": "Project",
},
]
}
/>
</Fragment>
`;

exports[`should render correctly: invalid config, admin user 1`] = `
<Fragment>
<CreateProjectPageHeader
additionalActions={
<div
className="display-flex-center pull-right"
>
<DeferredSpinner
className="spacer-right"
loading={false}
timeout={100}
/>
<Button
className="button-large button-primary"
disabled={true}
onClick={[MockFunction]}
>
onboarding.create_project.import_selected_repo
</Button>
</div>
}
showBreadcrumb={true}
title={
<span
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="24"
src="/images/alm/bitbucket.svg"
/>
onboarding.create_project.from_bbs
</span>
}
/>
<Alert
variant="error"
>
<FormattedMessage
defaultMessage="onboarding.create_project.no_bbs_binding.admin"
id="onboarding.create_project.no_bbs_binding.admin"
values={
Object {
"url": <Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/admin/settings",
"query": Object {
"category": "pull_request_decoration",
},
}
}
>
settings.page
</Link>,
}
}
/>
</Alert>
</Fragment>
`;

exports[`should render correctly: invalid config, regular user 1`] = `
<Fragment>
<CreateProjectPageHeader
additionalActions={
<div
className="display-flex-center pull-right"
>
<DeferredSpinner
className="spacer-right"
loading={false}
timeout={100}
/>
<Button
className="button-large button-primary"
disabled={true}
onClick={[MockFunction]}
>
onboarding.create_project.import_selected_repo
</Button>
</div>
}
showBreadcrumb={true}
title={
<span
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="24"
src="/images/alm/bitbucket.svg"
/>
onboarding.create_project.from_bbs
</span>
}
/>
<Alert
variant="error"
>
onboarding.create_project.no_bbs_binding
</Alert>
</Fragment>
`;

exports[`should render correctly: loading 1`] = `
<Fragment>
<CreateProjectPageHeader
additionalActions={
<div
className="display-flex-center pull-right"
>
<DeferredSpinner
className="spacer-right"
loading={false}
timeout={100}
/>
<Button
className="button-large button-primary"
disabled={true}
onClick={[MockFunction]}
>
onboarding.create_project.import_selected_repo
</Button>
</div>
}
showBreadcrumb={true}
title={
<span
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="24"
src="/images/alm/bitbucket.svg"
/>
onboarding.create_project.from_bbs
</span>
}
/>
<i
className="spinner"
/>
</Fragment>
`;

exports[`should render correctly: pat form 1`] = `
<Fragment>
<CreateProjectPageHeader
additionalActions={false}
showBreadcrumb={true}
title={
<span
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="24"
src="/images/alm/bitbucket.svg"
/>
onboarding.create_project.from_bbs
</span>
}
/>
<BitbucketPersonalAccessTokenForm
bitbucketSetting={
Object {
"alm": "bitbucket",
"key": "key",
}
}
onPersonalAccessTokenCreate={[MockFunction]}
/>
</Fragment>
`;

exports[`should render correctly: selected repo 1`] = `
<Fragment>
<CreateProjectPageHeader
additionalActions={
<div
className="display-flex-center pull-right"
>
<DeferredSpinner
className="spacer-right"
loading={false}
timeout={100}
/>
<Button
className="button-large button-primary"
disabled={false}
onClick={[MockFunction]}
>
onboarding.create_project.import_selected_repo
</Button>
</div>
}
showBreadcrumb={true}
title={
<span
className="text-middle"
>
<img
alt=""
className="spacer-right"
height="24"
src="/images/alm/bitbucket.svg"
/>
onboarding.create_project.from_bbs
</span>
}
/>
<BitbucketImportRepositoryForm
importing={false}
onSelectRepository={[MockFunction]}
projectRepositories={
Object {
"foo": Array [
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
},
],
}
}
projects={
Array [
Object {
"id": 1,
"key": "foo",
"name": "Project",
},
]
}
selectedRepository={
Object {
"id": 1,
"name": "Repo",
"projectKey": "project",
"slug": "project__repo",
}
}
/>
</Fragment>
`;

+ 267
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap Прегледај датотеку

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

exports[`should render correctly: default 1`] = `
<Fragment>
<header
className="huge-spacer-top big-spacer-bottom padded"
>
<h1
className="text-center huge big-spacer-bottom"
>
my_account.create_new.TRK
</h1>
<p
className="text-center big"
>
onboarding.create_project.select_method
</p>
</header>
<div
className="create-project-modes huge-spacer-top display-flex-space-around"
>
<button
className="button button-huge display-flex-column create-project-mode-type-manual"
onClick={[Function]}
type="button"
>
<img
alt=""
height={80}
src="/images/sonarcloud/analysis/manual.svg"
width={80}
/>
<div
className="medium big-spacer-top"
>
onboarding.create_project.select_method.manual
</div>
</button>
<button
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs"
disabled={false}
onClick={[Function]}
type="button"
>
<img
alt=""
height={80}
src="/images/alm/bitbucket.svg"
width={80}
/>
<div
className="medium big-spacer-top"
>
onboarding.create_project.select_method.from_bbs
</div>
</button>
</div>
</Fragment>
`;

exports[`should render correctly: loading bbs instances 1`] = `
<Fragment>
<header
className="huge-spacer-top big-spacer-bottom padded"
>
<h1
className="text-center huge big-spacer-bottom"
>
my_account.create_new.TRK
</h1>
<p
className="text-center big"
>
onboarding.create_project.select_method
</p>
</header>
<div
className="create-project-modes huge-spacer-top display-flex-space-around"
>
<button
className="button button-huge display-flex-column create-project-mode-type-manual"
onClick={[Function]}
type="button"
>
<img
alt=""
height={80}
src="/images/sonarcloud/analysis/manual.svg"
width={80}
/>
<div
className="medium big-spacer-top"
>
onboarding.create_project.select_method.manual
</div>
</button>
<button
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs"
disabled={false}
onClick={[Function]}
type="button"
>
<img
alt=""
height={80}
src="/images/alm/bitbucket.svg"
width={80}
/>
<div
className="medium big-spacer-top"
>
onboarding.create_project.select_method.from_bbs
</div>
<span>
onboarding.create_project.check_bbs_supported
<i
className="little-spacer-left spinner"
/>
</span>
</button>
</div>
</Fragment>
`;

exports[`should render correctly: no bbs instances 1`] = `
<Fragment>
<header
className="huge-spacer-top big-spacer-bottom padded"
>
<h1
className="text-center huge big-spacer-bottom"
>
my_account.create_new.TRK
</h1>
<p
className="text-center big"
>
onboarding.create_project.select_method
</p>
</header>
<div
className="create-project-modes huge-spacer-top display-flex-space-around"
>
<button
className="button button-huge display-flex-column create-project-mode-type-manual"
onClick={[Function]}
type="button"
>
<img
alt=""
height={80}
src="/images/sonarcloud/analysis/manual.svg"
width={80}
/>
<div
className="medium big-spacer-top"
>
onboarding.create_project.select_method.manual
</div>
</button>
<button
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs"
disabled={true}
onClick={[Function]}
type="button"
>
<img
alt=""
height={80}
src="/images/alm/bitbucket.svg"
width={80}
/>
<div
className="medium big-spacer-top"
>
onboarding.create_project.select_method.from_bbs
</div>
<div
className="text-muted small spacer-top"
style={
Object {
"lineHeight": 1.5,
}
}
>
onboarding.create_project.bbs_not_configured
<HelpTooltip
className="little-spacer-left"
overlay="onboarding.create_project.zero_bbs_instances"
/>
</div>
</button>
</div>
</Fragment>
`;

exports[`should render correctly: too many bbs instances 1`] = `
<Fragment>
<header
className="huge-spacer-top big-spacer-bottom padded"
>
<h1
className="text-center huge big-spacer-bottom"
>
my_account.create_new.TRK
</h1>
<p
className="text-center big"
>
onboarding.create_project.select_method
</p>
</header>
<div
className="create-project-modes huge-spacer-top display-flex-space-around"
>
<button
className="button button-huge display-flex-column create-project-mode-type-manual"
onClick={[Function]}
type="button"
>
<img
alt=""
height={80}
src="/images/sonarcloud/analysis/manual.svg"
width={80}
/>
<div
className="medium big-spacer-top"
>
onboarding.create_project.select_method.manual
</div>
</button>
<button
className="button button-huge big-spacer-left display-flex-column create-project-mode-type-bbs"
disabled={true}
onClick={[Function]}
type="button"
>
<img
alt=""
height={80}
src="/images/alm/bitbucket.svg"
width={80}
/>
<div
className="medium big-spacer-top"
>
onboarding.create_project.select_method.from_bbs
</div>
<div
className="text-muted small spacer-top"
style={
Object {
"lineHeight": 1.5,
}
}
>
onboarding.create_project.bbs_not_configured
<HelpTooltip
className="little-spacer-left"
overlay="onboarding.create_project.too_many_bbs_instances_X.2"
/>
</div>
</button>
</div>
</Fragment>
`;

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap Прегледај датотеку

@@ -41,7 +41,7 @@ exports[`should render correctly for SonarQube 1`] = `
<A11ySkipTarget
anchor="create_project_main"
/>
<Connect(withCurrentUser(whenLoggedIn(CreateProjectPageSonarQube)))
<Connect(withCurrentUser(whenLoggedIn(Connect(withAppState(CreateProjectPageSonarQube)))))
location={
Object {
"action": "PUSH",

+ 48
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPageHeader-test.tsx.snap Прегледај датотеку

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

exports[`should render correctly: additional content 1`] = `
<header
className="huge-spacer-bottom bordered-bottom overflow-hidden"
>
<h1
className="pull-left huge big-spacer-bottom"
>
Foo
</h1>
Bar
</header>
`;

exports[`should render correctly: default 1`] = `
<header
className="huge-spacer-bottom bordered-bottom overflow-hidden"
>
<h1
className="pull-left huge big-spacer-bottom"
>
Foo
</h1>
</header>
`;

exports[`should render correctly: with breadcrumb 1`] = `
<header
className="huge-spacer-bottom bordered-bottom overflow-hidden"
>
<h1
className="pull-left huge big-spacer-bottom"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to="/projects/create"
>
my_account.create_new.TRK
</Link>
<span
className="big-spacer-left big-spacer-right slash-separator"
/>
Foo
</h1>
</header>
`;

+ 114
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPageSonarQube-test.tsx.snap Прегледај датотеку

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

exports[`should render correctly 1`] = `
<Fragment>
<Helmet
defer={true}
encodeSpecialCharacters={true}
title="my_account.create_new.TRK"
titleTemplate="%s"
/>
<div
className="page page-limited huge-spacer-bottom position-relative"
id="create-project"
>
<CreateProjectModeSelection
bbsBindingCount={0}
loadingBindings={true}
onSelectMode={[Function]}
/>
</div>
</Fragment>
`;

exports[`should render correctly if no branch support 1`] = `
<Fragment>
<Helmet
defer={true}
encodeSpecialCharacters={true}
title="my_account.create_new.TRK"
titleTemplate="%s"
/>
<div
className="page page-limited huge-spacer-bottom position-relative"
id="create-project"
>
<ManualProjectCreate
branchesEnabled={false}
currentUser={
Object {
"groups": Array [],
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
"scmAccounts": Array [],
}
}
onProjectCreate={[Function]}
/>
</div>
</Fragment>
`;

exports[`should render correctly if the BBS method is selected 1`] = `
<Fragment>
<Helmet
defer={true}
encodeSpecialCharacters={true}
title="my_account.create_new.TRK"
titleTemplate="%s"
/>
<div
className="page page-limited huge-spacer-bottom position-relative"
id="create-project"
>
<Connect(BitbucketProjectCreate)
bitbucketSettings={Array []}
loadingBindings={true}
location={
Object {
"action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
"query": Object {
"mode": "bbs",
},
"search": "",
"state": Object {},
}
}
onProjectCreate={[Function]}
/>
</div>
</Fragment>
`;

exports[`should render correctly if the manual method is selected 1`] = `
<Fragment>
<Helmet
defer={true}
encodeSpecialCharacters={true}
title="my_account.create_new.TRK"
titleTemplate="%s"
/>
<div
className="page page-limited huge-spacer-bottom position-relative"
id="create-project"
>
<ManualProjectCreate
branchesEnabled={true}
currentUser={
Object {
"groups": Array [],
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
"scmAccounts": Array [],
}
}
onProjectCreate={[Function]}
/>
</div>
</Fragment>
`;

+ 77
- 72
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap Прегледај датотеку

@@ -1,85 +1,90 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="create-project"
>
<Fragment>
<CreateProjectPageHeader
title="onboarding.create_project.setup_manually"
/>
<div
className="flex-1 huge-spacer-right"
className="create-project"
>
<form
className="manual-project-create"
onSubmit={[Function]}
<div
className="flex-1 huge-spacer-right"
>
<withRouter(OrganizationInput)
onChange={[Function]}
organization=""
organizations={
Array [
Object {
"key": "foo",
"name": "Foo",
},
Object {
"key": "bar",
"name": "Bar",
"subscription": "PAID",
},
]
}
/>
<ValidationInput
className="form-field"
description="onboarding.create_project.project_key.description"
help="onboarding.create_project.project_key.help"
id="project-key"
isInvalid={false}
isValid={false}
label="onboarding.create_project.project_key"
required={true}
<form
className="manual-project-create"
onSubmit={[Function]}
>
<input
autoFocus={true}
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
<withRouter(OrganizationInput)
onChange={[Function]}
type="text"
value=""
organization=""
organizations={
Array [
Object {
"key": "foo",
"name": "Foo",
},
Object {
"key": "bar",
"name": "Bar",
"subscription": "PAID",
},
]
}
/>
</ValidationInput>
<ValidationInput
className="form-field"
description="onboarding.create_project.display_name.description"
help="onboarding.create_project.display_name.help"
id="project-name"
isInvalid={false}
isValid={false}
label="onboarding.create_project.display_name"
required={true}
>
<input
className="input-super-large"
<ValidationInput
className="form-field"
description="onboarding.create_project.project_key.description"
help="onboarding.create_project.project_key.help"
id="project-key"
isInvalid={false}
isValid={false}
label="onboarding.create_project.project_key"
required={true}
>
<input
autoFocus={true}
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
onChange={[Function]}
type="text"
value=""
/>
</ValidationInput>
<ValidationInput
className="form-field"
description="onboarding.create_project.display_name.description"
help="onboarding.create_project.display_name.help"
id="project-name"
maxLength={255}
minLength={1}
onChange={[Function]}
type="text"
value=""
isInvalid={false}
isValid={false}
label="onboarding.create_project.display_name"
required={true}
>
<input
className="input-super-large"
id="project-name"
maxLength={255}
minLength={1}
onChange={[Function]}
type="text"
value=""
/>
</ValidationInput>
<SubmitButton
disabled={true}
>
set_up
</SubmitButton>
<DeferredSpinner
className="spacer-left"
loading={false}
timeout={100}
/>
</ValidationInput>
<SubmitButton
disabled={true}
>
set_up
</SubmitButton>
<DeferredSpinner
className="spacer-left"
loading={false}
timeout={100}
/>
</form>
</form>
</div>
</div>
</div>
</Fragment>
`;

+ 37
- 0
server/sonar-web/src/main/js/apps/create/project/style.css Прегледај датотеку

@@ -17,6 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#create-project {
padding-top: 0 !important;
}

#create-project header {
padding-top: 20px;
}

.white-page #create-project header {
background-color: white;
position: sticky;
top: var(--globalNavHeight);
z-index: var(--pageMainZIndex);
}

.create-project {
display: flex !important;
justify-content: space-between;
@@ -124,3 +139,25 @@
.create-project-actions .icon-checkbox {
margin-right: 8px;
}

.create-project-modes {
margin: 0 auto;
max-width: 500px;
}

.create-project-import-bbs i.icon-radio {
flex-shrink: 0;
}

.create-project-import-bbs .open .boxed-group-header {
border-bottom: 1px solid var(--barBorderColor);
}

.create-project-import-bbs .boxed-group-inner {
padding-top: calc(3 * var(--gridSize));
}

.create-project-import-bbs-repo {
width: 250px;
min-height: 40px;
}

+ 23
- 0
server/sonar-web/src/main/js/apps/create/project/types.ts Прегледај датотеку

@@ -0,0 +1,23 @@
/*
* 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.
*/
export enum CreateProjectModes {
Manual = 'manual',
BitbucketServer = 'bbs'
}

+ 12
- 0
server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts Прегледај датотеку

@@ -18,12 +18,24 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import {
AlmSettingsInstance,
ALM_KEYS,
AzureBindingDefinition,
BitbucketBindingDefinition,
GithubBindingDefinition,
GitlabBindingDefinition
} from '../../types/alm-settings';

export function mockAlmSettingsInstance(
overrides: Partial<AlmSettingsInstance> = {}
): AlmSettingsInstance {
return {
alm: ALM_KEYS.GITHUB,
key: 'key',
...overrides
};
}

export function mockAzureDefinition(
overrides: Partial<AzureBindingDefinition> = {}
): AzureBindingDefinition {

+ 32
- 3
sonar-core/src/main/resources/org/sonar/l10n/core.properties Прегледај датотеку

@@ -40,6 +40,7 @@ close=Close
closed=Closed
code=Code
color=Color
collapse_all=Collapse all
compare=Compare
component=Component
configure=Configure
@@ -68,8 +69,9 @@ end_date=End Date
edit=Edit
events=Events
example=Example
extend=Extend
expand_all=Expand all
explore=Explore
extend=Extend
false=False
favorite=Favorite
file=File
@@ -3059,7 +3061,7 @@ onboarding.project_analysis.suggestions.bitbucket_extra=In case you need it, the
onboarding.project_analysis.suggestions.github=If you are using Travis CI, the SonarCloud Travis Add-on makes it easier to run these commands with your CI process.

onboarding.create_project.header=Analyze projects
onboarding.create_project.setup_manually=Set up manually
onboarding.create_project.setup_manually=Create manually
onboarding.create_project.create_new_org=Create another organization
onboarding.create_project.import_new_org=Import another organization
onboarding.create_project.install_app_description.bitbucket=We need you to install the SonarCloud Bitbucket application on one of your team in order to select which repositories you want to analyze.
@@ -3081,13 +3083,40 @@ onboarding.create_project.display_name=Display name
onboarding.create_project.display_name.error=The display name is required.
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 imported: {link}
onboarding.create_project.repository_imported=Already set up
onboarding.create_project.see_project=See the project
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
onboarding.create_project.encourage_to_subscribe=Subscribe your organization to our paid plan to get unlimited private projects.
onboarding.create_project.subscribtion_success_x={0} has been successfully upgraded to paid plan. You can now import and analyze private projects.
onboarding.create_project.from_bbs=From Bitbucket Server
onboarding.create_project.grant_access_to_bbs.title=Grant access to your repositories
onboarding.create_project.grant_access_to_bbs.help=SonarQube needs a personal access token to access and list your repositories from Bitbucket Server.
onboarding.create_project.select_method=How do you want to create your project?
onboarding.create_project.select_method.manual=Manually
onboarding.create_project.select_method.from_bbs=From a Bitbucket Server repository
onboarding.create_project.check_bbs_supported=Checking if available
onboarding.create_project.too_many_bbs_instances_X=You must have exactly 1 Bitbucket Server instance configured in order to use this method. You currently have {0}.
onboarding.create_project.zero_bbs_instances=You must first configure a Bitbucket Server instance.
onboarding.create_project.bbs_not_configured=This feature isn't available
onboarding.create_project.no_bbs_binding=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method, but none were found. Either create the project manually, or contact your system administrator.
onboarding.create_project.no_bbs_binding.admin=You must have exactly at least 1 Bitbucket Server instance configured in order to use this method. You can configure instances under {url}.
onboarding.create_project.enter_pat=Enter personal access token
onboarding.create_project.pat_help.title=How to create a personal access token?
onboarding.create_project.pat_help.bbs_help_1=Click the following link to generate a token in Bitbucket Server, and copy-paste it into the personal access token field.
onboarding.create_project.pat_help.bbs_help_2=Set a name, for example "SonarQube", and select the following permissions:
onboarding.create_project.pat_help.link=Create personal access token
onboarding.create_project.pat_help.bbs_permission_projects=Projects: {perm}
onboarding.create_project.pat_help.bbs_permission_repos=Repositories: {perm}
onboarding.create_project.pat_help.read_permission=Read
onboarding.create_project.error_fetching_bbs_projects=There was an error fetching the projects from Bitbucket Server. Contact your system administrator, or check your personal access token.
onboarding.create_project.error_fetching_bbs_repos=There was an error fetching the repositories from Bitbucket Server. Contact your system administrator, or check your personal access token.
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.import_selected_repo=Set up selected repository
onboarding.create_project.go_to_project=Go to project

onboarding.create_organization.page.header=Create Organization
onboarding.create_organization.page.description=An organization is a space where a team or a whole company can collaborate accross many projects.

Loading…
Откажи
Сачувај