Browse Source

SONAR-14393 Add admin form for Bitbucket Cloud integration

tags/8.7.0.41497
Wouter Admiraal 3 years ago
parent
commit
eaa3c93177
56 changed files with 1554 additions and 397 deletions
  1. 11
    0
      server/sonar-web/src/main/js/api/alm-settings.ts
  2. 1
    1
      server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx
  3. 8
    3
      server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx
  4. 2
    2
      server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
  5. 2
    2
      server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx
  6. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreate-test.tsx
  7. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreateRenderer-test.tsx
  8. 6
    3
      server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx
  9. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx
  10. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/PersonalAccessTokenForm-test.tsx
  11. 4
    2
      server/sonar-web/src/main/js/apps/create/project/__tests__/WrongBindingCountAlert-test.tsx
  12. 3
    5
      server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx
  13. 2
    2
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx
  14. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenuItem-test.tsx
  15. 18
    13
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx
  16. 3
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormModalRenderer.tsx
  17. 2
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
  18. 4
    4
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx
  19. 6
    5
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx
  20. 3
    2
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
  21. 122
    42
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketForm.tsx
  22. 139
    51
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx
  23. 140
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTabRenderer.tsx
  24. 2
    2
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx
  25. 23
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionBox-test.tsx
  26. 8
    4
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx
  27. 3
    2
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx
  28. 9
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx
  29. 25
    3
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketForm-test.tsx
  30. 112
    4
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx
  31. 63
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTabRenderer-test.tsx
  32. 146
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap
  33. 6
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormModalRenderer-test.tsx.snap
  34. 1
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap
  35. 85
    6
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap
  36. 0
    7
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap
  37. 83
    39
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
  38. 246
    72
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketForm-test.tsx.snap
  39. 25
    67
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap
  40. 143
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTabRenderer-test.tsx.snap
  41. 4
    4
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap
  42. 7
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/utils.ts
  43. 1
    1
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx
  44. 3
    2
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx
  45. 2
    2
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx
  46. 5
    5
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx
  47. 1
    1
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx
  48. 1
    1
      server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx
  49. 3
    3
      server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx
  50. 1
    1
      server/sonar-web/src/main/js/components/tutorials/jenkins/WebhookStep.tsx
  51. 1
    1
      server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/PreRequisitesStep-test.tsx
  52. 5
    1
      server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/WebhookStep-test.tsx
  53. 9
    1
      server/sonar-web/src/main/js/helpers/constants.ts
  54. 14
    1
      server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts
  55. 22
    15
      server/sonar-web/src/main/js/types/alm-settings.ts
  56. 14
    5
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

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

AzureBindingDefinition, AzureBindingDefinition,
AzureProjectAlmBindingParams, AzureProjectAlmBindingParams,
BitbucketBindingDefinition, BitbucketBindingDefinition,
BitbucketCloudBindingDefinition,
BitbucketProjectAlmBindingParams, BitbucketProjectAlmBindingParams,
GithubBindingDefinition, GithubBindingDefinition,
GithubProjectAlmBindingParams, GithubProjectAlmBindingParams,
return post('/api/alm_settings/update_bitbucket', data).catch(throwGlobalError); return post('/api/alm_settings/update_bitbucket', data).catch(throwGlobalError);
} }


export function createBitbucketCloudConfiguration(data: BitbucketCloudBindingDefinition) {
return post('/api/alm_settings/create_bitbucketcloud', data).catch(throwGlobalError);
}

export function updateBitbucketCloudConfiguration(
data: BitbucketCloudBindingDefinition & { newKey: string }
) {
return post('/api/alm_settings/update_bitbucketcloud', data).catch(throwGlobalError);
}

export function createGitlabConfiguration(data: GitlabBindingDefinition) { export function createGitlabConfiguration(data: GitlabBindingDefinition) {
return post('/api/alm_settings/create_gitlab', data).catch(throwGlobalError); return post('/api/alm_settings/create_gitlab', data).catch(throwGlobalError);
} }

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

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


{!loading && !bitbucketSetting && ( {!loading && !bitbucketSetting && (
<WrongBindingCountAlert alm={AlmKeys.Bitbucket} canAdmin={!!canAdmin} />
<WrongBindingCountAlert alm={AlmKeys.BitbucketServer} canAdmin={!!canAdmin} />
)} )}


{!loading && {!loading &&

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

import { CreateProjectModes } from './types'; import { CreateProjectModes } from './types';


export interface CreateProjectModeSelectionProps { export interface CreateProjectModeSelectionProps {
almCounts: { [key in AlmKeys]: number };
almCounts: {
[AlmKeys.Azure]: number;
[AlmKeys.BitbucketServer]: number;
[AlmKeys.GitLab]: number;
[AlmKeys.GitHub]: number;
};
appState: Pick<T.AppState, 'canAdmin'>; appState: Pick<T.AppState, 'canAdmin'>;
loadingBindings: boolean; loadingBindings: boolean;
onSelectMode: (mode: CreateProjectModes) => void; onSelectMode: (mode: CreateProjectModes) => void;


function renderAlmOption( function renderAlmOption(
props: CreateProjectModeSelectionProps, props: CreateProjectModeSelectionProps,
alm: AlmKeys,
alm: AlmKeys.Azure | AlmKeys.BitbucketServer | AlmKeys.GitHub | AlmKeys.GitLab,
mode: CreateProjectModes mode: CreateProjectModes
) { ) {
const { const {
</button> </button>


{renderAlmOption(props, AlmKeys.Azure, CreateProjectModes.AzureDevOps)} {renderAlmOption(props, AlmKeys.Azure, CreateProjectModes.AzureDevOps)}
{renderAlmOption(props, AlmKeys.Bitbucket, CreateProjectModes.BitbucketServer)}
{renderAlmOption(props, AlmKeys.BitbucketServer, CreateProjectModes.BitbucketServer)}
{renderAlmOption(props, AlmKeys.GitHub, CreateProjectModes.GitHub)} {renderAlmOption(props, AlmKeys.GitHub, CreateProjectModes.GitHub)}
{renderAlmOption(props, AlmKeys.GitLab, CreateProjectModes.GitLab)} {renderAlmOption(props, AlmKeys.GitLab, CreateProjectModes.GitLab)}
</div> </div>

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx View File

if (this.mounted) { if (this.mounted) {
this.setState({ this.setState({
azureSettings: almSettings.filter(s => s.alm === AlmKeys.Azure), azureSettings: almSettings.filter(s => s.alm === AlmKeys.Azure),
bitbucketSettings: almSettings.filter(s => s.alm === AlmKeys.Bitbucket),
bitbucketSettings: almSettings.filter(s => s.alm === AlmKeys.BitbucketServer),
githubSettings: almSettings.filter(s => s.alm === AlmKeys.GitHub), githubSettings: almSettings.filter(s => s.alm === AlmKeys.GitHub),
gitlabSettings: almSettings.filter(s => s.alm === AlmKeys.GitLab), gitlabSettings: almSettings.filter(s => s.alm === AlmKeys.GitLab),
loading: false loading: false
default: { default: {
const almCounts = { const almCounts = {
[AlmKeys.Azure]: azureSettings.length, [AlmKeys.Azure]: azureSettings.length,
[AlmKeys.Bitbucket]: bitbucketSettings.length,
[AlmKeys.BitbucketServer]: bitbucketSettings.length,
[AlmKeys.GitHub]: githubSettings.length, [AlmKeys.GitHub]: githubSettings.length,
[AlmKeys.GitLab]: gitlabSettings.length [AlmKeys.GitLab]: gitlabSettings.length
}; };

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx View File

} }


function getPatUrl(alm: AlmKeys, url: string) { function getPatUrl(alm: AlmKeys, url: string) {
if (alm === AlmKeys.Bitbucket) {
if (alm === AlmKeys.BitbucketServer) {
return `${url.replace(/\/$/, '')}/plugins/servlet/access-tokens/add`; return `${url.replace(/\/$/, '')}/plugins/servlet/access-tokens/add`;
} else { } else {
// GitLab // GitLab
</p> </p>


<ul> <ul>
{alm === AlmKeys.Bitbucket && (
{alm === AlmKeys.BitbucketServer && (
<> <>
<li> <li>
<FormattedMessage <FormattedMessage

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

return shallow<BitbucketProjectCreate>( return shallow<BitbucketProjectCreate>(
<BitbucketProjectCreate <BitbucketProjectCreate
canAdmin={false} canAdmin={false}
bitbucketSettings={[mockAlmSettingsInstance({ alm: AlmKeys.Bitbucket, key: 'foo' })]}
bitbucketSettings={[mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer, key: 'foo' })]}
loadingBindings={false} loadingBindings={false}
location={mockLocation()} location={mockLocation()}
onProjectCreate={jest.fn()} onProjectCreate={jest.fn()}

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

function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {}) { function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {}) {
return shallow<BitbucketProjectCreateRendererProps>( return shallow<BitbucketProjectCreateRendererProps>(
<BitbucketProjectCreateRenderer <BitbucketProjectCreateRenderer
bitbucketSetting={mockAlmSettingsInstance({ alm: AlmKeys.Bitbucket })}
bitbucketSetting={mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer })}
importing={false} importing={false}
loading={false} loading={false}
onImportRepository={jest.fn()} onImportRepository={jest.fn()}

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

it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default'); expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ loadingBindings: true })).toMatchSnapshot('loading instances'); expect(shallowRender({ loadingBindings: true })).toMatchSnapshot('loading instances');
expect(shallowRender({}, { [AlmKeys.Bitbucket]: 0, [AlmKeys.GitHub]: 2 })).toMatchSnapshot(
expect(shallowRender({}, { [AlmKeys.BitbucketServer]: 0, [AlmKeys.GitHub]: 2 })).toMatchSnapshot(
'invalid configs, not admin' 'invalid configs, not admin'
); );
expect( expect(
shallowRender({ appState: { canAdmin: true } }, { [AlmKeys.Bitbucket]: 0, [AlmKeys.GitHub]: 2 })
shallowRender(
{ appState: { canAdmin: true } },
{ [AlmKeys.BitbucketServer]: 0, [AlmKeys.GitHub]: 2 }
)
).toMatchSnapshot('invalid configs, admin'); ).toMatchSnapshot('invalid configs, admin');
}); });


) { ) {
const almCounts = { const almCounts = {
[AlmKeys.Azure]: 0, [AlmKeys.Azure]: 0,
[AlmKeys.Bitbucket]: 1,
[AlmKeys.BitbucketServer]: 1,
[AlmKeys.GitHub]: 0, [AlmKeys.GitHub]: 0,
[AlmKeys.GitLab]: 0, [AlmKeys.GitLab]: 0,
...almCountOverrides ...almCountOverrides

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

import { CreateProjectModes } from '../types'; import { CreateProjectModes } from '../types';


jest.mock('../../../../api/alm-settings', () => ({ jest.mock('../../../../api/alm-settings', () => ({
getAlmSettings: jest.fn().mockResolvedValue([{ alm: AlmKeys.Bitbucket, key: 'foo' }])
getAlmSettings: jest.fn().mockResolvedValue([{ alm: AlmKeys.BitbucketServer, key: 'foo' }])
})); }));


beforeEach(jest.clearAllMocks); beforeEach(jest.clearAllMocks);

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

return shallow<PersonalAccessTokenFormProps>( return shallow<PersonalAccessTokenFormProps>(
<PersonalAccessTokenForm <PersonalAccessTokenForm
almSetting={mockAlmSettingsInstance({ almSetting={mockAlmSettingsInstance({
alm: AlmKeys.Bitbucket,
alm: AlmKeys.BitbucketServer,
url: 'http://www.example.com' url: 'http://www.example.com'
})} })}
onPersonalAccessTokenCreate={jest.fn()} onPersonalAccessTokenCreate={jest.fn()}

+ 4
- 2
server/sonar-web/src/main/js/apps/create/project/__tests__/WrongBindingCountAlert-test.tsx View File



it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender({ canAdmin: true })).toMatchSnapshot('for admin'); expect(shallowRender({ canAdmin: true })).toMatchSnapshot('for admin');
expect(shallowRender({ alm: AlmKeys.Bitbucket })).toMatchSnapshot('bitbucket');
expect(shallowRender({ alm: AlmKeys.BitbucketServer })).toMatchSnapshot('bitbucket');
expect(shallowRender({ alm: AlmKeys.GitLab })).toMatchSnapshot('gitlab'); expect(shallowRender({ alm: AlmKeys.GitLab })).toMatchSnapshot('gitlab');
}); });


function shallowRender(props: Partial<WrongBindingCountAlertProps> = {}) { function shallowRender(props: Partial<WrongBindingCountAlertProps> = {}) {
return shallow(<WrongBindingCountAlert alm={AlmKeys.Bitbucket} canAdmin={false} {...props} />);
return shallow(
<WrongBindingCountAlert alm={AlmKeys.BitbucketServer} canAdmin={false} {...props} />
);
} }

+ 3
- 5
server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx View File

import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { getAlmSettings } from '../../../api/alm-settings'; import { getAlmSettings } from '../../../api/alm-settings';
import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
import { IMPORT_COMPATIBLE_ALMS } from '../../../helpers/constants';
import { hasGlobalPermission } from '../../../helpers/users'; import { hasGlobalPermission } from '../../../helpers/users';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
import ProjectCreationMenuItem from './ProjectCreationMenuItem'; import ProjectCreationMenuItem from './ProjectCreationMenuItem';
} }


const PROJECT_CREATION_PERMISSION = 'provisioning'; const PROJECT_CREATION_PERMISSION = 'provisioning';
/*
* ALMs for which the import feature has been implemented
*/
const IMPORT_COMPATIBLE_ALMS = [AlmKeys.Azure, AlmKeys.Bitbucket, AlmKeys.GitHub, AlmKeys.GitLab];


const almSettingsValidators = { const almSettingsValidators = {
[AlmKeys.Azure]: (settings: AlmSettingsInstance) => !!settings.url, [AlmKeys.Azure]: (settings: AlmSettingsInstance) => !!settings.url,
[AlmKeys.Bitbucket]: (_: AlmSettingsInstance) => true,
[AlmKeys.BitbucketServer]: (_: AlmSettingsInstance) => true,
[AlmKeys.BitbucketCloud]: (_: AlmSettingsInstance) => false,
[AlmKeys.GitHub]: (_: AlmSettingsInstance) => true, [AlmKeys.GitHub]: (_: AlmSettingsInstance) => true,
[AlmKeys.GitLab]: (settings: AlmSettingsInstance) => !!settings.url [AlmKeys.GitLab]: (settings: AlmSettingsInstance) => !!settings.url
}; };

+ 2
- 2
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx View File

it('should filter alm bindings appropriately', async () => { it('should filter alm bindings appropriately', async () => {
(getAlmSettings as jest.Mock).mockResolvedValueOnce([ (getAlmSettings as jest.Mock).mockResolvedValueOnce([
{ alm: AlmKeys.Azure }, { alm: AlmKeys.Azure },
{ alm: AlmKeys.Bitbucket, url: 'b1' },
{ alm: AlmKeys.Bitbucket, url: 'b2' },
{ alm: AlmKeys.BitbucketServer, url: 'b1' },
{ alm: AlmKeys.BitbucketServer, url: 'b2' },
{ alm: AlmKeys.GitHub }, { alm: AlmKeys.GitHub },
{ alm: AlmKeys.GitLab, url: 'gitlab.com' } { alm: AlmKeys.GitLab, url: 'gitlab.com' }
]); ]);

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenuItem-test.tsx View File

}); });


function shallowRender(overrides: Partial<ProjectCreationMenuItemProps> = {}) { function shallowRender(overrides: Partial<ProjectCreationMenuItemProps> = {}) {
return shallow(<ProjectCreationMenuItem alm={AlmKeys.Bitbucket} {...overrides} />);
return shallow(<ProjectCreationMenuItem alm={AlmKeys.BitbucketServer} {...overrides} />);
} }

+ 18
- 13
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx View File

import { Alert } from 'sonar-ui-common/components/ui/Alert'; import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { getEdition, getEditionUrl } from '../../../../helpers/editions'; import { getEdition, getEditionUrl } from '../../../../helpers/editions';
import { IMPORT_COMPATIBLE_ALMS } from '../../../../helpers/constants';
import { import {
AlmBindingDefinition, AlmBindingDefinition,
AlmKeys, AlmKeys,
export default function AlmBindingDefinitionBox(props: AlmBindingDefinitionBoxProps) { export default function AlmBindingDefinitionBox(props: AlmBindingDefinitionBoxProps) {
const { alm, branchesEnabled, definition, multipleDefinitions, status = DEFAULT_STATUS } = props; const { alm, branchesEnabled, definition, multipleDefinitions, status = DEFAULT_STATUS } = props;


const importFeatureTitle =
const prDecoFeatureTitle =
alm === AlmKeys.GitLab alm === AlmKeys.GitLab
? translate('settings.almintegration.feature.mr_decoration.title') ? translate('settings.almintegration.feature.mr_decoration.title')
: translate('settings.almintegration.feature.pr_decoration.title'); : translate('settings.almintegration.feature.pr_decoration.title');


const importFeatureDescription =
const prDecoFeatureDescription =
alm === AlmKeys.GitLab alm === AlmKeys.GitLab
? translate('settings.almintegration.feature.mr_decoration.description') ? translate('settings.almintegration.feature.mr_decoration.description')
: translate('settings.almintegration.feature.pr_decoration.description'); : translate('settings.almintegration.feature.pr_decoration.description');
{status.type !== AlmSettingsBindingStatusType.Warning && ( {status.type !== AlmSettingsBindingStatusType.Warning && (
<div className="display-flex-row spacer-bottom"> <div className="display-flex-row spacer-bottom">
<div className="huge-spacer-right"> <div className="huge-spacer-right">
<Tooltip overlay={importFeatureDescription}>
<span>{importFeatureTitle}</span>
<Tooltip overlay={prDecoFeatureDescription}>
<span>{prDecoFeatureTitle}</span>
</Tooltip> </Tooltip>
{getPRDecorationFeatureStatus(branchesEnabled, status.type)} {getPRDecorationFeatureStatus(branchesEnabled, status.type)}
</div> </div>
<div>
<Tooltip
overlay={translate(
'settings.almintegration.feature.alm_repo_import.description'
)}>
<span>{translate('settings.almintegration.feature.alm_repo_import.title')}</span>
</Tooltip>
{getImportFeatureStatus(definition, multipleDefinitions, status.type)}
</div>
{IMPORT_COMPATIBLE_ALMS.includes(alm) && (
<div>
<Tooltip
overlay={translate(
'settings.almintegration.feature.alm_repo_import.description'
)}>
<span>
{translate('settings.almintegration.feature.alm_repo_import.title')}
</span>
</Tooltip>
{getImportFeatureStatus(definition, multipleDefinitions, status.type)}
</div>
)}
</div> </div>
)} )}



+ 3
- 1
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormModalRenderer.tsx View File

<div className="display-flex-start"> <div className="display-flex-start">
<div className="flex-1">{children}</div> <div className="flex-1">{children}</div>


{help && (
{help ? (
<Alert className="huge-spacer-left flex-1" variant="info"> <Alert className="huge-spacer-left flex-1" variant="info">
{help} {help}
</Alert> </Alert>
) : (
<div className="flex-1" />
)} )}
</div> </div>
</div> </div>

+ 2
- 1
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx View File

currentAlm: props.location.query.alm || AlmKeys.GitHub, currentAlm: props.location.query.alm || AlmKeys.GitHub,
definitions: { definitions: {
[AlmKeys.Azure]: [], [AlmKeys.Azure]: [],
[AlmKeys.Bitbucket]: [],
[AlmKeys.BitbucketServer]: [],
[AlmKeys.BitbucketCloud]: [],
[AlmKeys.GitHub]: [], [AlmKeys.GitHub]: [],
[AlmKeys.GitLab]: [] [AlmKeys.GitLab]: []
}, },

+ 4
- 4
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx View File

requiresBranchesEnabled: false requiresBranchesEnabled: false
}, },
{ {
key: AlmKeys.Bitbucket,
key: AlmKeys.BitbucketServer,
label: ( label: (
<> <>
<img <img
height={16} height={16}
src={`${getBaseUrl()}/images/alm/bitbucket.svg`} src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
/> />
Bitbucket Server
Bitbucket
</> </>
), ),
requiresBranchesEnabled: false requiresBranchesEnabled: false
onUpdateDefinitions={props.onUpdateDefinitions} onUpdateDefinitions={props.onUpdateDefinitions}
/> />
)} )}
{currentAlm === AlmKeys.Bitbucket && (
{currentAlm === AlmKeys.BitbucketServer && (
<BitbucketTab <BitbucketTab
branchesEnabled={branchesEnabled} branchesEnabled={branchesEnabled}
definitions={definitions.bitbucket}
definitions={[...definitions.bitbucket, ...definitions.bitbucketcloud]}
definitionStatus={definitionStatus} definitionStatus={definitionStatus}
loadingAlmDefinitions={loadingAlmDefinitions} loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount} loadingProjectCount={loadingProjectCount}

+ 6
- 5
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx View File

} }


handleCancel = () => { handleCancel = () => {
this.setState({
editedDefinition: undefined,
success: false
});
this.setState({ editedDefinition: undefined, success: false });
}; };


handleCreate = () => { handleCreate = () => {
return call return call
.then(() => { .then(() => {
if (this.mounted) { if (this.mounted) {
this.setState({ editedDefinition: undefined, submitting: false, success: true });
this.setState({
editedDefinition: undefined,
submitting: false,
success: true
});
} }
}) })
.then(this.props.onUpdateDefinitions) .then(this.props.onUpdateDefinitions)

+ 3
- 2
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx View File

import { import {
AlmBindingDefinition, AlmBindingDefinition,
AlmKeys, AlmKeys,
AlmSettingsBindingStatus
AlmSettingsBindingStatus,
isBitbucketCloudBindingDefinition
} from '../../../../types/alm-settings'; } from '../../../../types/alm-settings';
import AlmBindingDefinitionBox from './AlmBindingDefinitionBox'; import AlmBindingDefinitionBox from './AlmBindingDefinitionBox';
import AlmBindingDefinitionForm, { import AlmBindingDefinitionForm, {
</div> </div>
{definitions.map(def => ( {definitions.map(def => (
<AlmBindingDefinitionBox <AlmBindingDefinitionBox
alm={alm}
alm={isBitbucketCloudBindingDefinition(def) ? AlmKeys.BitbucketCloud : alm}
branchesEnabled={branchesEnabled} branchesEnabled={branchesEnabled}
definition={def} definition={def}
key={def.key} key={def.key}

+ 122
- 42
server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketForm.tsx View File

*/ */
import * as React from 'react'; import * as React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import RadioToggle from 'sonar-ui-common/components/controls/RadioToggle';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { BitbucketBindingDefinition } from '../../../../types/alm-settings';
import {
AlmKeys,
BitbucketBindingDefinition,
BitbucketCloudBindingDefinition,
isBitbucketBindingDefinition,
isBitbucketCloudBindingDefinition
} from '../../../../types/alm-settings';
import { AlmBindingDefinitionFormField } from './AlmBindingDefinitionFormField'; import { AlmBindingDefinitionFormField } from './AlmBindingDefinitionFormField';


export interface BitbucketFormProps { export interface BitbucketFormProps {
formData: BitbucketBindingDefinition;
hideKeyField?: boolean;
onFieldChange: (fieldId: keyof BitbucketBindingDefinition, value: string) => void;
readOnly?: boolean;
formData: BitbucketBindingDefinition | BitbucketCloudBindingDefinition;
isCreating: boolean;
onFieldChange: (
fieldId: keyof (BitbucketBindingDefinition & BitbucketCloudBindingDefinition),
value: string
) => void;
onSelectVariant: (variant: AlmKeys.BitbucketServer | AlmKeys.BitbucketCloud) => void;
variant?: AlmKeys.BitbucketServer | AlmKeys.BitbucketCloud;
} }


export default function BitbucketForm(props: BitbucketFormProps) { export default function BitbucketForm(props: BitbucketFormProps) {
const { formData, hideKeyField, onFieldChange, readOnly } = props;
const { formData, isCreating, variant } = props;


return ( return (
<>
{!hideKeyField && (
<AlmBindingDefinitionFormField
autoFocus={true}
help={translate('settings.almintegration.form.name.bitbucket.help')}
id="name.bitbucket"
maxLength={100}
onFieldChange={onFieldChange}
propKey="key"
readOnly={readOnly}
value={formData.key}
/>
<div>
{isCreating && (
<>
<strong>{translate('settings.almintegration.form.choose_bitbucket_variant')}</strong>
<RadioToggle
className="little-spacer-top big-spacer-bottom"
name="variant"
onCheck={props.onSelectVariant}
options={[
{
label: 'Bitbucket Server',
value: AlmKeys.BitbucketServer
},
{ label: 'Bitbucket Cloud', value: AlmKeys.BitbucketCloud }
]}
value={variant}
/>
</>
)} )}
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage={translate('settings.almintegration.form.url.bitbucket.help')}
id="settings.almintegration.form.url.bitbucket.help"
values={{ example: 'https://bitbucket-server.your-company.com' }}

{variant === AlmKeys.BitbucketServer && isBitbucketBindingDefinition(formData) && (
<div>
<AlmBindingDefinitionFormField
autoFocus={true}
help={translate('settings.almintegration.form.name.bitbucket.help')}
id="name.bitbucket"
maxLength={100}
onFieldChange={props.onFieldChange}
propKey="key"
value={formData.key}
/>
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage={translate('settings.almintegration.form.url.bitbucket.help')}
id="settings.almintegration.form.url.bitbucket.help"
values={{ example: 'https://bitbucket-server.your-company.com' }}
/>
}
id="url.bitbucket"
maxLength={2000}
onFieldChange={props.onFieldChange}
propKey="url"
value={formData.url}
/> />
}
id="url.bitbucket"
maxLength={2000}
onFieldChange={onFieldChange}
propKey="url"
readOnly={readOnly}
value={formData.url}
/>
<AlmBindingDefinitionFormField
id="personal_access_token"
isTextArea={true}
onFieldChange={onFieldChange}
overwriteOnly={Boolean(formData.key)}
propKey="personalAccessToken"
readOnly={readOnly}
value={formData.personalAccessToken}
/>
</>
<AlmBindingDefinitionFormField
id="personal_access_token"
isTextArea={true}
onFieldChange={props.onFieldChange}
overwriteOnly={Boolean(formData.key)}
propKey="personalAccessToken"
value={formData.personalAccessToken}
/>
</div>
)}

{variant === AlmKeys.BitbucketCloud && isBitbucketCloudBindingDefinition(formData) && (
<div>
<AlmBindingDefinitionFormField
autoFocus={true}
help={translate('settings.almintegration.form.name.bitbucketcloud.help')}
id="name.bitbucket"
maxLength={100}
onFieldChange={props.onFieldChange}
propKey="key"
value={formData.key}
/>
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage={translate(
'settings.almintegration.form.workspace.bitbucketcloud.help'
)}
id="settings.almintegration.form.workspace.bitbucketcloud.help"
values={{
example: (
<>
{'https://bitbucket.org/'}
<strong>{'{workspace}'}</strong>
{'/{repository}'}
</>
)
}}
/>
}
id="workspace.bitbucketcloud"
maxLength={2000}
onFieldChange={props.onFieldChange}
propKey="workspace"
value={formData.workspace}
/>
<AlmBindingDefinitionFormField
id="client_id.bitbucketcloud"
onFieldChange={props.onFieldChange}
propKey="clientId"
value={formData.clientId}
/>
<AlmBindingDefinitionFormField
id="client_secret.bitbucketcloud"
onFieldChange={props.onFieldChange}
overwriteOnly={Boolean(formData.key)}
propKey="clientSecret"
value={formData.clientSecret}
/>
</div>
)}
</div>
); );
} }

+ 139
- 51
server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx View File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; import * as React from 'react';
import { Link } from 'react-router';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { import {
createBitbucketCloudConfiguration,
createBitbucketConfiguration, createBitbucketConfiguration,
updateBitbucketCloudConfiguration,
updateBitbucketConfiguration updateBitbucketConfiguration
} from '../../../../api/alm-settings'; } from '../../../../api/alm-settings';
import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { import {
AlmKeys, AlmKeys,
AlmSettingsBindingStatus, AlmSettingsBindingStatus,
BitbucketBindingDefinition
BitbucketBindingDefinition,
BitbucketCloudBindingDefinition,
isBitbucketBindingDefinition
} from '../../../../types/alm-settings'; } from '../../../../types/alm-settings';
import AlmTab from './AlmTab';
import BitbucketForm from './BitbucketForm';
import BitbucketTabRenderer from './BitbucketTabRenderer';


export interface BitbucketTabProps {
interface Props {
branchesEnabled: boolean; branchesEnabled: boolean;
definitions: BitbucketBindingDefinition[];
definitions: Array<BitbucketBindingDefinition | BitbucketCloudBindingDefinition>;
definitionStatus: T.Dict<AlmSettingsBindingStatus>; definitionStatus: T.Dict<AlmSettingsBindingStatus>;
loadingAlmDefinitions: boolean; loadingAlmDefinitions: boolean;
loadingProjectCount: boolean; loadingProjectCount: boolean;
onUpdateDefinitions: () => void; onUpdateDefinitions: () => void;
} }


export default function BitbucketTab(props: BitbucketTabProps) {
const {
branchesEnabled,
multipleAlmEnabled,
definitions,
definitionStatus,
loadingAlmDefinitions,
loadingProjectCount
} = props;

return (
<div className="bordered">
<AlmTab
alm={AlmKeys.Bitbucket}
interface State {
editedDefinition?: BitbucketBindingDefinition | BitbucketCloudBindingDefinition;
isCreating: boolean;
submitting: boolean;
success: boolean;
variant?: AlmKeys.BitbucketServer | AlmKeys.BitbucketCloud;
}

export const DEFAULT_SERVER_BINDING = { key: '', url: '', personalAccessToken: '' };
export const DEFAULT_CLOUD_BINDING = { key: '', clientId: '', clientSecret: '', workspace: '' };

export default class BitbucketTab extends React.PureComponent<Props, State> {
mounted = false;
state: State = { isCreating: false, submitting: false, success: false };

componentDidMount() {
this.mounted = true;
}

componentWillUnmount() {
this.mounted = false;
}

handleCancel = () => {
this.setState({
editedDefinition: undefined,
isCreating: false,
success: false,
variant: undefined
});
};

handleCreate = () => {
this.setState({
editedDefinition: DEFAULT_SERVER_BINDING, // Default to Bitbucket Server.
isCreating: true,
success: false,
variant: undefined
});
};

handleSelectVariant = (variant: AlmKeys.BitbucketServer | AlmKeys.BitbucketCloud) => {
this.setState({
variant,
editedDefinition:
variant === AlmKeys.BitbucketServer ? DEFAULT_SERVER_BINDING : DEFAULT_CLOUD_BINDING
});
};

handleEdit = (definitionKey: string) => {
const editedDefinition = this.props.definitions.find(d => d.key === definitionKey);
const variant = isBitbucketBindingDefinition(editedDefinition)
? AlmKeys.BitbucketServer
: AlmKeys.BitbucketCloud;
this.setState({ editedDefinition, variant, success: false });
};

handleSubmit = (
config: BitbucketBindingDefinition | BitbucketCloudBindingDefinition,
originalKey: string
) => {
const call = originalKey
? this.updateConfiguration({ newKey: config.key, ...config, key: originalKey })
: this.createConfiguration({ ...config });

this.setState({ submitting: true });
return call
.then(() => {
if (this.mounted) {
this.setState({
editedDefinition: undefined,
isCreating: false,
submitting: false,
success: true
});
}
})
.then(this.props.onUpdateDefinitions)
.then(() => {
this.props.onCheck(config.key);
})
.catch(() => {
if (this.mounted) {
this.setState({ submitting: false, success: false });
}
});
};

updateConfiguration = (
config: (BitbucketBindingDefinition | BitbucketCloudBindingDefinition) & { newKey: string }
) => {
if (isBitbucketBindingDefinition(config)) {
return updateBitbucketConfiguration(config);
}
return updateBitbucketCloudConfiguration(config);
};

createConfiguration = (config: BitbucketBindingDefinition | BitbucketCloudBindingDefinition) => {
if (isBitbucketBindingDefinition(config)) {
return createBitbucketConfiguration(config);
}
return createBitbucketCloudConfiguration(config);
};

render() {
const {
branchesEnabled,
definitions,
definitionStatus,
loadingAlmDefinitions,
loadingProjectCount,
multipleAlmEnabled
} = this.props;
const { editedDefinition, isCreating, submitting, success, variant } = this.state;

return (
<BitbucketTabRenderer
branchesEnabled={branchesEnabled} branchesEnabled={branchesEnabled}
createConfiguration={createBitbucketConfiguration}
defaultBinding={{ key: '', url: '', personalAccessToken: '' }}
definitions={definitions} definitions={definitions}
definitionStatus={definitionStatus} definitionStatus={definitionStatus}
form={childProps => <BitbucketForm {...childProps} />}
help={
<>
<h3>{translate('onboarding.create_project.pat_help.title')}</h3>

<p className="big-spacer-top">
{translate('settings.almintegration.bitbucket.help_1')}
</p>

<ul className="big-spacer-top list-styled">
<li>{translate('settings.almintegration.bitbucket.help_2')}</li>
<li>{translate('settings.almintegration.bitbucket.help_3')}</li>
</ul>

<p className="big-spacer-top big-spacer-bottom">
<Link target="_blank" to={ALM_DOCUMENTATION_PATHS[AlmKeys.Bitbucket]}>
{translate('learn_more')}
</Link>
</p>
</>
}
editedDefinition={editedDefinition}
isCreating={isCreating}
loadingAlmDefinitions={loadingAlmDefinitions} loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount} loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled} multipleAlmEnabled={multipleAlmEnabled}
onCheck={props.onCheck}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateBitbucketConfiguration}
onCancel={this.handleCancel}
onCheck={this.props.onCheck}
onCreate={this.handleCreate}
onDelete={this.props.onDelete}
onEdit={this.handleEdit}
onSelectVariant={this.handleSelectVariant}
onSubmit={this.handleSubmit}
submitting={submitting}
success={success}
variant={variant}
/> />
</div>
);
);
}
} }

+ 140
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTabRenderer.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2021 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import {
AlmKeys,
AlmSettingsBindingStatus,
BitbucketBindingDefinition,
BitbucketCloudBindingDefinition
} from '../../../../types/alm-settings';
import AlmTabRenderer from './AlmTabRenderer';
import BitbucketForm from './BitbucketForm';

export interface BitbucketTabRendererProps {
branchesEnabled: boolean;
definitionStatus: T.Dict<AlmSettingsBindingStatus>;
editedDefinition?: BitbucketBindingDefinition | BitbucketCloudBindingDefinition;
definitions: Array<BitbucketBindingDefinition | BitbucketCloudBindingDefinition>;
isCreating: boolean;
loadingAlmDefinitions: boolean;
loadingProjectCount: boolean;
multipleAlmEnabled: boolean;
onCancel: () => void;
onCheck: (definitionKey: string) => void;
onCreate: () => void;
onDelete: (definitionKey: string) => void;
onEdit: (definitionKey: string) => void;
onSelectVariant: (variant: AlmKeys.BitbucketServer | AlmKeys.BitbucketCloud) => void;
onSubmit: (
config: BitbucketBindingDefinition | BitbucketCloudBindingDefinition,
originalKey: string
) => void;
submitting: boolean;
success: boolean;
variant?: AlmKeys.BitbucketServer | AlmKeys.BitbucketCloud;
}

export default function BitbucketTabRenderer(props: BitbucketTabRendererProps) {
const {
branchesEnabled,
editedDefinition,
definitions,
definitionStatus,
isCreating,
loadingAlmDefinitions,
loadingProjectCount,
multipleAlmEnabled,
submitting,
success,
variant
} = props;

let help;
if (variant === AlmKeys.BitbucketServer) {
help = (
<>
<h3>{translate('onboarding.create_project.pat_help.title')}</h3>

<p className="big-spacer-top">{translate('settings.almintegration.bitbucket.help_1')}</p>

<ul className="big-spacer-top list-styled">
<li>{translate('settings.almintegration.bitbucket.help_2')}</li>
<li>{translate('settings.almintegration.bitbucket.help_3')}</li>
</ul>

<p className="big-spacer-top big-spacer-bottom">
<Link target="_blank" to={ALM_DOCUMENTATION_PATHS[AlmKeys.BitbucketServer]}>
{translate('learn_more')}
</Link>
</p>
</>
);
} else if (variant === AlmKeys.BitbucketCloud) {
help = (
<FormattedMessage
defaultMessage={translate(`settings.almintegration.bitbucketcloud.info`)}
id="settings.almintegration.bitbucketcloud.info"
values={{
link: (
<Link target="_blank" to={ALM_DOCUMENTATION_PATHS[AlmKeys.BitbucketCloud]}>
{translate('learn_more')}
</Link>
)
}}
/>
);
}

return (
<div className="bordered">
<AlmTabRenderer
branchesEnabled={branchesEnabled}
alm={AlmKeys.BitbucketServer} // Always use Bitbucket Server for the translation keys.
definitions={definitions}
definitionStatus={definitionStatus}
editedDefinition={editedDefinition}
form={childProps => (
<BitbucketForm
isCreating={isCreating}
onSelectVariant={props.onSelectVariant}
variant={variant}
{...childProps}
/>
)}
help={help}
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
onCancel={props.onCancel}
onCheck={props.onCheck}
onCreate={props.onCreate}
onDelete={props.onDelete}
onEdit={props.onEdit}
onSubmit={props.onSubmit}
submitting={submitting}
success={success}
/>
</div>
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx View File

value={formData.appId} value={formData.appId}
/> />
<AlmBindingDefinitionFormField <AlmBindingDefinitionFormField
id="client_id"
id="client_id.github"
maxLength={80} maxLength={80}
onFieldChange={onFieldChange} onFieldChange={onFieldChange}
propKey="clientId" propKey="clientId"
value={formData.clientId} value={formData.clientId}
/> />
<AlmBindingDefinitionFormField <AlmBindingDefinitionFormField
id="client_secret"
id="client_secret.github"
maxLength={80} maxLength={80}
onFieldChange={onFieldChange} onFieldChange={onFieldChange}
overwriteOnly={Boolean(formData.key)} overwriteOnly={Boolean(formData.key)}

+ 23
- 1
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionBox-test.tsx View File

import { import {
mockAlmSettingsBindingStatus, mockAlmSettingsBindingStatus,
mockAzureBindingDefinition, mockAzureBindingDefinition,
mockGithubBindingDefinition
mockBitbucketCloudBindingDefinition,
mockGithubBindingDefinition,
mockGitlabBindingDefinition
} from '../../../../../helpers/mocks/alm-settings'; } from '../../../../../helpers/mocks/alm-settings';
import { AlmKeys, AlmSettingsBindingStatusType } from '../../../../../types/alm-settings'; import { AlmKeys, AlmSettingsBindingStatusType } from '../../../../../types/alm-settings';
import AlmBindingDefinitionBox, { AlmBindingDefinitionBoxProps } from '../AlmBindingDefinitionBox'; import AlmBindingDefinitionBox, { AlmBindingDefinitionBoxProps } from '../AlmBindingDefinitionBox';
shallowRender({ alm: AlmKeys.Azure, definition: mockAzureBindingDefinition() }) shallowRender({ alm: AlmKeys.Azure, definition: mockAzureBindingDefinition() })
).toMatchSnapshot('Azure DevOps'); ).toMatchSnapshot('Azure DevOps');


expect(
shallowRender({
status: mockAlmSettingsBindingStatus({
type: AlmSettingsBindingStatusType.Success
}),
alm: AlmKeys.GitLab,
definition: mockGitlabBindingDefinition()
})
).toMatchSnapshot('success for GitLab');

expect(
shallowRender({
status: mockAlmSettingsBindingStatus({
type: AlmSettingsBindingStatusType.Success
}),
alm: AlmKeys.BitbucketCloud,
definition: mockBitbucketCloudBindingDefinition()
})
).toMatchSnapshot('success for Bitbucket Cloud');

expect( expect(
shallowRender({ shallowRender({
branchesEnabled: false, branchesEnabled: false,

+ 8
- 4
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx View File

deleteConfiguration: jest.fn().mockResolvedValue(undefined), deleteConfiguration: jest.fn().mockResolvedValue(undefined),
getAlmDefinitions: jest getAlmDefinitions: jest
.fn() .fn()
.mockResolvedValue({ azure: [], bitbucket: [], github: [], gitlab: [] }),
.mockResolvedValue({ azure: [], bitbucket: [], bitbucketcloud: [], github: [], gitlab: [] }),
validateAlmSettings: jest.fn().mockResolvedValue('') validateAlmSettings: jest.fn().mockResolvedValue('')
})); }));


it('should validate existing configurations', async () => { it('should validate existing configurations', async () => {
(getAlmDefinitions as jest.Mock).mockResolvedValueOnce({ (getAlmDefinitions as jest.Mock).mockResolvedValueOnce({
[AlmKeys.Azure]: [{ key: 'a1' }], [AlmKeys.Azure]: [{ key: 'a1' }],
[AlmKeys.Bitbucket]: [{ key: 'b1' }],
[AlmKeys.BitbucketServer]: [{ key: 'b1' }],
[AlmKeys.BitbucketCloud]: [{ key: 'bc1' }],
[AlmKeys.GitHub]: [{ key: 'gh1' }, { key: 'gh2' }], [AlmKeys.GitHub]: [{ key: 'gh1' }, { key: 'gh2' }],
[AlmKeys.GitLab]: [{ key: 'gl1' }] [AlmKeys.GitLab]: [{ key: 'gl1' }]
}); });


await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);


expect(validateAlmSettings).toBeCalledTimes(5);
expect(validateAlmSettings).toBeCalledTimes(6);
expect(validateAlmSettings).toBeCalledWith('a1'); expect(validateAlmSettings).toBeCalledWith('a1');
expect(validateAlmSettings).toBeCalledWith('b1'); expect(validateAlmSettings).toBeCalledWith('b1');
expect(validateAlmSettings).toBeCalledWith('bc1');
expect(validateAlmSettings).toBeCalledWith('gh1'); expect(validateAlmSettings).toBeCalledWith('gh1');
expect(validateAlmSettings).toBeCalledWith('gh2'); expect(validateAlmSettings).toBeCalledWith('gh2');
expect(validateAlmSettings).toBeCalledWith('gl1'); expect(validateAlmSettings).toBeCalledWith('gl1');
(validateAlmSettings as jest.Mock) (validateAlmSettings as jest.Mock)
.mockRejectedValueOnce(undefined) .mockRejectedValueOnce(undefined)
.mockResolvedValueOnce(failureMessage) .mockResolvedValueOnce(failureMessage)
.mockResolvedValueOnce('')
.mockResolvedValueOnce(''); .mockResolvedValueOnce('');


await wrapper.instance().handleCheck(definitionKey); await wrapper.instance().handleCheck(definitionKey);
it('should fetch settings', async () => { it('should fetch settings', async () => {
const definitions = { const definitions = {
[AlmKeys.Azure]: [{ key: 'a1' }], [AlmKeys.Azure]: [{ key: 'a1' }],
[AlmKeys.Bitbucket]: [{ key: 'b1' }],
[AlmKeys.BitbucketServer]: [{ key: 'b1' }],
[AlmKeys.BitbucketCloud]: [{ key: 'bc1' }],
[AlmKeys.GitHub]: [{ key: 'gh1' }], [AlmKeys.GitHub]: [{ key: 'gh1' }],
[AlmKeys.GitLab]: [{ key: 'gl1' }] [AlmKeys.GitLab]: [{ key: 'gl1' }]
}; };

+ 3
- 2
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx View File

'delete modal' 'delete modal'
); );
expect(shallowRender({ currentAlm: AlmKeys.Azure })).toMatchSnapshot('azure'); expect(shallowRender({ currentAlm: AlmKeys.Azure })).toMatchSnapshot('azure');
expect(shallowRender({ currentAlm: AlmKeys.Bitbucket })).toMatchSnapshot('bitbucket');
expect(shallowRender({ currentAlm: AlmKeys.BitbucketServer })).toMatchSnapshot('bitbucket');
expect(shallowRender({ currentAlm: AlmKeys.BitbucketCloud })).toMatchSnapshot('bitbucketcloud');
expect(shallowRender({ currentAlm: AlmKeys.GitLab })).toMatchSnapshot('gitlab'); expect(shallowRender({ currentAlm: AlmKeys.GitLab })).toMatchSnapshot('gitlab');
}); });


<AlmIntegrationRenderer <AlmIntegrationRenderer
branchesEnabled={true} branchesEnabled={true}
currentAlm={AlmKeys.GitHub} currentAlm={AlmKeys.GitHub}
definitions={{ azure: [], bitbucket: [], github: [], gitlab: [] }}
definitions={{ azure: [], bitbucket: [], bitbucketcloud: [], github: [], gitlab: [] }}
definitionStatus={{}} definitionStatus={{}}
loadingAlmDefinitions={false} loadingAlmDefinitions={false}
loadingProjectCount={false} loadingProjectCount={false}

+ 9
- 1
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx View File

import * as React from 'react'; import * as React from 'react';
import { import {
mockAzureBindingDefinition, mockAzureBindingDefinition,
mockBitbucketCloudBindingDefinition,
mockGithubBindingDefinition mockGithubBindingDefinition
} from '../../../../../helpers/mocks/alm-settings'; } from '../../../../../helpers/mocks/alm-settings';
import { import {
defaultBinding: mockGithubBindingDefinition(), defaultBinding: mockGithubBindingDefinition(),
definitions: [mockGithubBindingDefinition()] definitions: [mockGithubBindingDefinition()]
}; };
expect(shallowRender(githubProps)).toMatchSnapshot();
expect(shallowRender(githubProps)).toMatchSnapshot('default');
expect(shallowRender({ ...githubProps, definitions: [] })).toMatchSnapshot('empty'); expect(shallowRender({ ...githubProps, definitions: [] })).toMatchSnapshot('empty');


expect( expect(
editedDefinition: mockGithubBindingDefinition() editedDefinition: mockGithubBindingDefinition()
}) })
).toMatchSnapshot('create a first'); ).toMatchSnapshot('create a first');

expect(
shallowRender({
alm: AlmKeys.BitbucketServer, // BitbucketServer will be passed for both Bitbucket variants.
definitions: [mockBitbucketCloudBindingDefinition()]
})
).toMatchSnapshot('pass the correct key for bitbucket cloud');
}); });


function shallowRenderAzure(props: Partial<AlmTabRendererProps<AzureBindingDefinition>> = {}) { function shallowRenderAzure(props: Partial<AlmTabRendererProps<AzureBindingDefinition>> = {}) {

+ 25
- 3
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketForm-test.tsx View File

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockBitbucketBindingDefinition } from '../../../../../helpers/mocks/alm-settings';
import {
mockBitbucketBindingDefinition,
mockBitbucketCloudBindingDefinition
} from '../../../../../helpers/mocks/alm-settings';
import { AlmKeys } from '../../../../../types/alm-settings';
import BitbucketForm, { BitbucketFormProps } from '../BitbucketForm'; import BitbucketForm, { BitbucketFormProps } from '../BitbucketForm';


it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender({ formData: mockBitbucketBindingDefinition() })).toMatchSnapshot();
expect(shallowRender({ isCreating: true })).toMatchSnapshot('variant select');
expect(shallowRender()).toMatchSnapshot('bitbucket server, empty');
expect(shallowRender({ formData: mockBitbucketBindingDefinition() })).toMatchSnapshot(
'bitbucket server, edit'
);
expect(
shallowRender({
formData: { key: '', clientId: '', clientSecret: '', workspace: '' },
variant: AlmKeys.BitbucketCloud
})
).toMatchSnapshot('bitbucket cloud, empty');
expect(
shallowRender({
variant: AlmKeys.BitbucketCloud,
formData: mockBitbucketCloudBindingDefinition()
})
).toMatchSnapshot('bitbucket cloud, edit');
}); });


function shallowRender(props: Partial<BitbucketFormProps> = {}) { function shallowRender(props: Partial<BitbucketFormProps> = {}) {
return shallow( return shallow(
<BitbucketForm <BitbucketForm
formData={{ key: '', personalAccessToken: '', url: '' }} formData={{ key: '', personalAccessToken: '', url: '' }}
isCreating={false}
onFieldChange={jest.fn()} onFieldChange={jest.fn()}
onSelectVariant={jest.fn()}
variant={AlmKeys.BitbucketServer}
{...props} {...props}
/> />
); );

+ 112
- 4
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx View File

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockBitbucketBindingDefinition } from '../../../../../helpers/mocks/alm-settings';
import BitbucketTab, { BitbucketTabProps } from '../BitbucketTab';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import {
createBitbucketCloudConfiguration,
createBitbucketConfiguration,
updateBitbucketCloudConfiguration,
updateBitbucketConfiguration
} from '../../../../../api/alm-settings';
import {
mockBitbucketBindingDefinition,
mockBitbucketCloudBindingDefinition
} from '../../../../../helpers/mocks/alm-settings';
import { AlmKeys } from '../../../../../types/alm-settings';
import BitbucketTab, { DEFAULT_CLOUD_BINDING, DEFAULT_SERVER_BINDING } from '../BitbucketTab';

jest.mock('../../../../../api/alm-settings', () => ({
createBitbucketConfiguration: jest.fn().mockResolvedValue(null),
createBitbucketCloudConfiguration: jest.fn().mockResolvedValue(null),
updateBitbucketConfiguration: jest.fn().mockResolvedValue(null),
updateBitbucketCloudConfiguration: jest.fn().mockResolvedValue(null)
}));


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


function shallowRender(props: Partial<BitbucketTabProps> = {}) {
return shallow(
it('should handle cancel', async () => {
const wrapper = shallowRender();

wrapper.setState({
editedDefinition: mockBitbucketBindingDefinition()
});

wrapper.instance().handleCancel();

await waitAndUpdate(wrapper);

expect(wrapper.state().editedDefinition).toBeUndefined();
});

it('should handle edit', async () => {
const config = mockBitbucketBindingDefinition();
const wrapper = shallowRender({ definitions: [config] });
wrapper.instance().handleEdit(config.key);
await waitAndUpdate(wrapper);
expect(wrapper.state().editedDefinition).toEqual(config);
});

it('should create config for Bitbucket Server', async () => {
const onUpdateDefinitions = jest.fn();
const config = mockBitbucketBindingDefinition();
const wrapper = shallowRender({ onUpdateDefinitions });

wrapper.instance().handleCreate();
wrapper.instance().handleSelectVariant(AlmKeys.BitbucketServer);
expect(wrapper.state().editedDefinition).toBe(DEFAULT_SERVER_BINDING);

wrapper.setState({ editedDefinition: config });
await wrapper.instance().handleSubmit(config, '');

expect(createBitbucketConfiguration).toBeCalledWith(config);
expect(onUpdateDefinitions).toBeCalled();
expect(wrapper.state().editedDefinition).toBeUndefined();
});

it('should create config for Bitbucket Cloud', async () => {
const onUpdateDefinitions = jest.fn();
const config = mockBitbucketCloudBindingDefinition();
const wrapper = shallowRender({ onUpdateDefinitions });

wrapper.instance().handleCreate();
wrapper.instance().handleSelectVariant(AlmKeys.BitbucketCloud);
expect(wrapper.state().editedDefinition).toBe(DEFAULT_CLOUD_BINDING);

wrapper.setState({ editedDefinition: config });
await wrapper.instance().handleSubmit(config, '');

expect(createBitbucketCloudConfiguration).toBeCalledWith(config);
expect(onUpdateDefinitions).toBeCalled();
expect(wrapper.state().editedDefinition).toBeUndefined();
});

it('should update config for Bitbucket Server', async () => {
const onUpdateDefinitions = jest.fn();
const config = mockBitbucketBindingDefinition();
const wrapper = shallowRender({ onUpdateDefinitions });
wrapper.setState({ editedDefinition: config });

await wrapper.instance().handleSubmit(config, 'originalKey');

expect(updateBitbucketConfiguration).toBeCalledWith({
newKey: 'key',
...config,
key: 'originalKey'
});
expect(onUpdateDefinitions).toBeCalled();
expect(wrapper.state().editedDefinition).toBeUndefined();
});

it('should update config for Bitbucket Cloud', async () => {
const onUpdateDefinitions = jest.fn();
const config = mockBitbucketCloudBindingDefinition();
const wrapper = shallowRender({ onUpdateDefinitions });
wrapper.setState({ editedDefinition: config });

await wrapper.instance().handleSubmit(config, 'originalKey');

expect(updateBitbucketCloudConfiguration).toBeCalledWith({
newKey: 'key',
...config,
key: 'originalKey'
});
expect(onUpdateDefinitions).toBeCalled();
expect(wrapper.state().editedDefinition).toBeUndefined();
});

function shallowRender(props: Partial<BitbucketTab['props']> = {}) {
return shallow<BitbucketTab>(
<BitbucketTab <BitbucketTab
branchesEnabled={true} branchesEnabled={true}
definitions={[mockBitbucketBindingDefinition()]} definitions={[mockBitbucketBindingDefinition()]}

+ 63
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTabRenderer-test.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2021 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockBitbucketBindingDefinition } from '../../../../../helpers/mocks/alm-settings';
import { AlmKeys, BitbucketBindingDefinition } from '../../../../../types/alm-settings';
import AlmTabRenderer, { AlmTabRendererProps } from '../AlmTabRenderer';
import BitbucketTabRenderer, { BitbucketTabRendererProps } from '../BitbucketTabRenderer';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ variant: AlmKeys.BitbucketServer })).toMatchSnapshot('bitbucket server');
expect(shallowRender({ variant: AlmKeys.BitbucketCloud })).toMatchSnapshot('bitbucket cloud');

const almTab = shallowRender().find<AlmTabRendererProps<BitbucketBindingDefinition>>(
AlmTabRenderer
);
expect(
almTab.props().form({ formData: mockBitbucketBindingDefinition(), onFieldChange: jest.fn() })
).toMatchSnapshot('bitbucket form');
});

function shallowRender(props: Partial<BitbucketTabRendererProps> = {}) {
return shallow<BitbucketTabRendererProps>(
<BitbucketTabRenderer
branchesEnabled={true}
definitions={[]}
definitionStatus={{}}
isCreating={false}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
onCancel={jest.fn()}
onCheck={jest.fn()}
onCreate={jest.fn()}
onDelete={jest.fn()}
onEdit={jest.fn()}
onSelectVariant={jest.fn()}
onSubmit={jest.fn()}
submitting={true}
success={false}
variant={undefined}
{...props}
/>
);
}

+ 146
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap View File

</div> </div>
`; `;


exports[`should render correctly: success for Bitbucket Cloud 1`] = `
<div
className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
>
<div
className="actions pull-right"
>
<Button
onClick={[Function]}
>
<EditIcon
className="spacer-right"
/>
edit
</Button>
<Button
className="button-red spacer-left"
onClick={[Function]}
>
<DeleteIcon
className="spacer-right"
/>
delete
</Button>
</div>
<div
className="big-spacer-bottom"
>
<h3>
key
</h3>
</div>
<div
className="display-flex-row spacer-bottom"
>
<div
className="huge-spacer-right"
>
<Tooltip
overlay="settings.almintegration.feature.pr_decoration.description"
>
<span>
settings.almintegration.feature.pr_decoration.title
</span>
</Tooltip>
<AlertSuccessIcon
className="spacer-left"
/>
</div>
</div>
<div
className="width-50"
/>
<Button
className="big-spacer-top"
onClick={[Function]}
>
settings.almintegration.check_configuration
</Button>
</div>
`;

exports[`should render correctly: success for GitLab 1`] = `
<div
className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
>
<div
className="actions pull-right"
>
<Button
onClick={[Function]}
>
<EditIcon
className="spacer-right"
/>
edit
</Button>
<Button
className="button-red spacer-left"
onClick={[Function]}
>
<DeleteIcon
className="spacer-right"
/>
delete
</Button>
</div>
<div
className="big-spacer-bottom"
>
<h3>
foo
</h3>
</div>
<div
className="display-flex-row spacer-bottom"
>
<div
className="huge-spacer-right"
>
<Tooltip
overlay="settings.almintegration.feature.mr_decoration.description"
>
<span>
settings.almintegration.feature.mr_decoration.title
</span>
</Tooltip>
<AlertSuccessIcon
className="spacer-left"
/>
</div>
<div>
<Tooltip
overlay="settings.almintegration.feature.alm_repo_import.description"
>
<span>
settings.almintegration.feature.alm_repo_import.title
</span>
</Tooltip>
<div
className="display-inline-flex-center"
>
<strong
className="spacer-left"
>
settings.almintegration.feature.alm_repo_import.disabled
</strong>
<HelpTooltip
className="little-spacer-left"
overlay="settings.almintegration.feature.alm_repo_import.disabled.no_url"
/>
</div>
</div>
</div>
<div
className="width-50"
/>
<Button
className="big-spacer-top"
onClick={[Function]}
>
settings.almintegration.check_configuration
</Button>
</div>
`;

exports[`should render correctly: success with alert 1`] = ` exports[`should render correctly: success with alert 1`] = `
<div <div
className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition" className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"

+ 6
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormModalRenderer-test.tsx.snap View File

> >
<Component /> <Component />
</div> </div>
<div
className="flex-1"
/>
</div> </div>
</div> </div>
<div <div
> >
<Component /> <Component />
</div> </div>
<div
className="flex-1"
/>
</div> </div>
</div> </div>
<div <div

+ 1
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap View File

Object { Object {
"azure": Array [], "azure": Array [],
"bitbucket": Array [], "bitbucket": Array [],
"bitbucketcloud": Array [],
"github": Array [], "github": Array [],
"gitlab": Array [], "gitlab": Array [],
} }

+ 85
- 6
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap View File

height={16} height={16}
src="/images/alm/bitbucket.svg" src="/images/alm/bitbucket.svg"
/> />
Bitbucket Server
Bitbucket
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": false, "requiresBranchesEnabled": false,
}, },
height={16} height={16}
src="/images/alm/bitbucket.svg" src="/images/alm/bitbucket.svg"
/> />
Bitbucket Server
Bitbucket
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": false, "requiresBranchesEnabled": false,
}, },
</Fragment> </Fragment>
`; `;


exports[`should render correctly: bitbucketcloud 1`] = `
<Fragment>
<header
className="page-header"
>
<h1
className="page-title"
>
settings.almintegration.title
</h1>
</header>
<div
className="markdown small spacer-top big-spacer-bottom"
>
settings.almintegration.description
</div>
<BoxedTabs
onSelect={[MockFunction]}
selected="bitbucketcloud"
tabs={
Array [
Object {
"key": "github",
"label": <React.Fragment>
<img
alt="github"
className="spacer-right"
height={16}
src="/images/alm/github.svg"
/>
GitHub
</React.Fragment>,
"requiresBranchesEnabled": false,
},
Object {
"key": "bitbucket",
"label": <React.Fragment>
<img
alt="bitbucket"
className="spacer-right"
height={16}
src="/images/alm/bitbucket.svg"
/>
Bitbucket
</React.Fragment>,
"requiresBranchesEnabled": false,
},
Object {
"key": "azure",
"label": <React.Fragment>
<img
alt="azure"
className="spacer-right"
height={16}
src="/images/alm/azure.svg"
/>
Azure DevOps
</React.Fragment>,
"requiresBranchesEnabled": false,
},
Object {
"key": "gitlab",
"label": <React.Fragment>
<img
alt="gitlab"
className="spacer-right"
height={16}
src="/images/alm/gitlab.svg"
/>
GitLab
</React.Fragment>,
"requiresBranchesEnabled": false,
},
]
}
/>
</Fragment>
`;

exports[`should render correctly: default 1`] = ` exports[`should render correctly: default 1`] = `
<Fragment> <Fragment>
<header <header
height={16} height={16}
src="/images/alm/bitbucket.svg" src="/images/alm/bitbucket.svg"
/> />
Bitbucket Server
Bitbucket
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": false, "requiresBranchesEnabled": false,
}, },
height={16} height={16}
src="/images/alm/bitbucket.svg" src="/images/alm/bitbucket.svg"
/> />
Bitbucket Server
Bitbucket
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": false, "requiresBranchesEnabled": false,
}, },
height={16} height={16}
src="/images/alm/bitbucket.svg" src="/images/alm/bitbucket.svg"
/> />
Bitbucket Server
Bitbucket
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": false, "requiresBranchesEnabled": false,
}, },
height={16} height={16}
src="/images/alm/bitbucket.svg" src="/images/alm/bitbucket.svg"
/> />
Bitbucket Server
Bitbucket
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": false, "requiresBranchesEnabled": false,
}, },

+ 0
- 7
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap View File

<AlmTabRenderer <AlmTabRenderer
alm="azure" alm="azure"
branchesEnabled={true} branchesEnabled={true}
defaultBinding={
Object {
"key": "",
"personalAccessToken": "",
"url": undefined,
}
}
definitionStatus={Object {}} definitionStatus={Object {}}
definitions={ definitions={
Array [ Array [

+ 83
- 39
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap View File

</div> </div>
`; `;


exports[`should render correctly with validation 1`] = `
exports[`should render correctly with validation: create a first 1`] = `
<div <div
className="big-padded" className="big-padded"
> >
<DeferredSpinner <DeferredSpinner
loading={false} loading={false}
> >
<p
className="spacer-top"
>
settings.almintegration.empty.github
</p>
<div <div
className="spacer-bottom text-right"
className="big-spacer-top"
> >
<Connect(withAppState(CreationTooltip)) <Connect(withAppState(CreationTooltip))
alm="github" alm="github"
</Button> </Button>
</Connect(withAppState(CreationTooltip))> </Connect(withAppState(CreationTooltip))>
</div> </div>
<AlmBindingDefinitionBox
alm="github"
branchesEnabled={true}
definition={
<AlmBindingDefinitionForm
bindingDefinition={
Object { Object {
"appId": "123456", "appId": "123456",
"clientId": "client1", "clientId": "client1",
"url": "http://github.enterprise.com", "url": "http://github.enterprise.com",
} }
} }
key="key"
multipleDefinitions={false}
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
help={<div />}
isSecondInstance={false}
onCancel={[MockFunction]}
onSubmit={[MockFunction]}
>
<Component />
</AlmBindingDefinitionForm>
</DeferredSpinner> </DeferredSpinner>
</div> </div>
`; `;


exports[`should render correctly with validation: create a first 1`] = `
exports[`should render correctly with validation: create a second 1`] = `
<div <div
className="big-padded" className="big-padded"
> >
<DeferredSpinner <DeferredSpinner
loading={false} loading={false}
> >
<p
className="spacer-top"
>
settings.almintegration.empty.github
</p>
<div <div
className="big-spacer-top"
className="spacer-bottom text-right"
> >
<Connect(withAppState(CreationTooltip)) <Connect(withAppState(CreationTooltip))
alm="github" alm="github"
</Button> </Button>
</Connect(withAppState(CreationTooltip))> </Connect(withAppState(CreationTooltip))>
</div> </div>
<AlmBindingDefinitionBox
alm="github"
branchesEnabled={true}
definition={
Object {
"appId": "123456",
"clientId": "client1",
"clientSecret": "**clientsecret**",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
key="key"
multipleDefinitions={false}
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
<AlmBindingDefinitionForm <AlmBindingDefinitionForm
bindingDefinition={ bindingDefinition={
Object { Object {
} }
} }
help={<div />} help={<div />}
isSecondInstance={false}
isSecondInstance={true}
onCancel={[MockFunction]} onCancel={[MockFunction]}
onSubmit={[MockFunction]} onSubmit={[MockFunction]}
> >
</div> </div>
`; `;


exports[`should render correctly with validation: create a second 1`] = `
exports[`should render correctly with validation: default 1`] = `
<div <div
className="big-padded" className="big-padded"
> >
onDelete={[MockFunction]} onDelete={[MockFunction]}
onEdit={[MockFunction]} onEdit={[MockFunction]}
/> />
<AlmBindingDefinitionForm
bindingDefinition={
Object {
"appId": "123456",
"clientId": "client1",
"clientSecret": "**clientsecret**",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
help={<div />}
isSecondInstance={true}
onCancel={[MockFunction]}
onSubmit={[MockFunction]}
>
<Component />
</AlmBindingDefinitionForm>
</DeferredSpinner> </DeferredSpinner>
</div> </div>
`; `;
</DeferredSpinner> </DeferredSpinner>
</div> </div>
`; `;

exports[`should render correctly with validation: pass the correct key for bitbucket cloud 1`] = `
<div
className="big-padded"
>
<DeferredSpinner
loading={false}
>
<div
className="spacer-bottom text-right"
>
<Connect(withAppState(CreationTooltip))
alm="bitbucket"
preventCreation={false}
>
<Button
data-test="settings__alm-create"
disabled={false}
onClick={[MockFunction]}
>
settings.almintegration.create
</Button>
</Connect(withAppState(CreationTooltip))>
</div>
<AlmBindingDefinitionBox
alm="bitbucketcloud"
branchesEnabled={true}
definition={
Object {
"clientId": "client1",
"clientSecret": "**clientsecret**",
"key": "key",
"workspace": "workspace",
}
}
key="key"
multipleDefinitions={false}
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
</DeferredSpinner>
</div>
`;

+ 246
- 72
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketForm-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should render correctly 1`] = `
<Fragment>
<AlmBindingDefinitionFormField
autoFocus={true}
help="settings.almintegration.form.name.bitbucket.help"
id="name.bitbucket"
maxLength={100}
onFieldChange={[MockFunction]}
propKey="key"
value=""
/>
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage="settings.almintegration.form.url.bitbucket.help"
id="settings.almintegration.form.url.bitbucket.help"
values={
Object {
"example": "https://bitbucket-server.your-company.com",
exports[`should render correctly: bitbucket cloud, edit 1`] = `
<div>
<div>
<AlmBindingDefinitionFormField
autoFocus={true}
help="settings.almintegration.form.name.bitbucketcloud.help"
id="name.bitbucket"
maxLength={100}
onFieldChange={[MockFunction]}
propKey="key"
value="key"
/>
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage="settings.almintegration.form.workspace.bitbucketcloud.help"
id="settings.almintegration.form.workspace.bitbucketcloud.help"
values={
Object {
"example": <React.Fragment>
https://bitbucket.org/
<strong>
{workspace}
</strong>
/{repository}
</React.Fragment>,
}
} }
}
/>
}
id="url.bitbucket"
maxLength={2000}
onFieldChange={[MockFunction]}
propKey="url"
value=""
/>
<AlmBindingDefinitionFormField
id="personal_access_token"
isTextArea={true}
onFieldChange={[MockFunction]}
overwriteOnly={false}
propKey="personalAccessToken"
value=""
/>
</Fragment>
/>
}
id="workspace.bitbucketcloud"
maxLength={2000}
onFieldChange={[MockFunction]}
propKey="workspace"
value="workspace"
/>
<AlmBindingDefinitionFormField
id="client_id.bitbucketcloud"
onFieldChange={[MockFunction]}
propKey="clientId"
value="client1"
/>
<AlmBindingDefinitionFormField
id="client_secret.bitbucketcloud"
onFieldChange={[MockFunction]}
overwriteOnly={true}
propKey="clientSecret"
value="**clientsecret**"
/>
</div>
</div>
`; `;


exports[`should render correctly 2`] = `
<Fragment>
<AlmBindingDefinitionFormField
autoFocus={true}
help="settings.almintegration.form.name.bitbucket.help"
id="name.bitbucket"
maxLength={100}
onFieldChange={[MockFunction]}
propKey="key"
value="key"
/>
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage="settings.almintegration.form.url.bitbucket.help"
id="settings.almintegration.form.url.bitbucket.help"
values={
Object {
"example": "https://bitbucket-server.your-company.com",
exports[`should render correctly: bitbucket cloud, empty 1`] = `
<div>
<div>
<AlmBindingDefinitionFormField
autoFocus={true}
help="settings.almintegration.form.name.bitbucketcloud.help"
id="name.bitbucket"
maxLength={100}
onFieldChange={[MockFunction]}
propKey="key"
value=""
/>
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage="settings.almintegration.form.workspace.bitbucketcloud.help"
id="settings.almintegration.form.workspace.bitbucketcloud.help"
values={
Object {
"example": <React.Fragment>
https://bitbucket.org/
<strong>
{workspace}
</strong>
/{repository}
</React.Fragment>,
}
}
/>
}
id="workspace.bitbucketcloud"
maxLength={2000}
onFieldChange={[MockFunction]}
propKey="workspace"
value=""
/>
<AlmBindingDefinitionFormField
id="client_id.bitbucketcloud"
onFieldChange={[MockFunction]}
propKey="clientId"
value=""
/>
<AlmBindingDefinitionFormField
id="client_secret.bitbucketcloud"
onFieldChange={[MockFunction]}
overwriteOnly={false}
propKey="clientSecret"
value=""
/>
</div>
</div>
`;

exports[`should render correctly: bitbucket server, edit 1`] = `
<div>
<div>
<AlmBindingDefinitionFormField
autoFocus={true}
help="settings.almintegration.form.name.bitbucket.help"
id="name.bitbucket"
maxLength={100}
onFieldChange={[MockFunction]}
propKey="key"
value="key"
/>
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage="settings.almintegration.form.url.bitbucket.help"
id="settings.almintegration.form.url.bitbucket.help"
values={
Object {
"example": "https://bitbucket-server.your-company.com",
}
} }
}
/>
/>
}
id="url.bitbucket"
maxLength={2000}
onFieldChange={[MockFunction]}
propKey="url"
value="http://bbs.enterprise.com"
/>
<AlmBindingDefinitionFormField
id="personal_access_token"
isTextArea={true}
onFieldChange={[MockFunction]}
overwriteOnly={true}
propKey="personalAccessToken"
value="asdf1234"
/>
</div>
</div>
`;

exports[`should render correctly: bitbucket server, empty 1`] = `
<div>
<div>
<AlmBindingDefinitionFormField
autoFocus={true}
help="settings.almintegration.form.name.bitbucket.help"
id="name.bitbucket"
maxLength={100}
onFieldChange={[MockFunction]}
propKey="key"
value=""
/>
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage="settings.almintegration.form.url.bitbucket.help"
id="settings.almintegration.form.url.bitbucket.help"
values={
Object {
"example": "https://bitbucket-server.your-company.com",
}
}
/>
}
id="url.bitbucket"
maxLength={2000}
onFieldChange={[MockFunction]}
propKey="url"
value=""
/>
<AlmBindingDefinitionFormField
id="personal_access_token"
isTextArea={true}
onFieldChange={[MockFunction]}
overwriteOnly={false}
propKey="personalAccessToken"
value=""
/>
</div>
</div>
`;

exports[`should render correctly: variant select 1`] = `
<div>
<strong>
settings.almintegration.form.choose_bitbucket_variant
</strong>
<RadioToggle
className="little-spacer-top big-spacer-bottom"
disabled={false}
name="variant"
onCheck={[MockFunction]}
options={
Array [
Object {
"label": "Bitbucket Server",
"value": "bitbucket",
},
Object {
"label": "Bitbucket Cloud",
"value": "bitbucketcloud",
},
]
} }
id="url.bitbucket"
maxLength={2000}
onFieldChange={[MockFunction]}
propKey="url"
value="http://bbs.enterprise.com"
/>
<AlmBindingDefinitionFormField
id="personal_access_token"
isTextArea={true}
onFieldChange={[MockFunction]}
overwriteOnly={true}
propKey="personalAccessToken"
value="asdf1234"
value="bitbucket"
/> />
</Fragment>
<div>
<AlmBindingDefinitionFormField
autoFocus={true}
help="settings.almintegration.form.name.bitbucket.help"
id="name.bitbucket"
maxLength={100}
onFieldChange={[MockFunction]}
propKey="key"
value=""
/>
<AlmBindingDefinitionFormField
help={
<FormattedMessage
defaultMessage="settings.almintegration.form.url.bitbucket.help"
id="settings.almintegration.form.url.bitbucket.help"
values={
Object {
"example": "https://bitbucket-server.your-company.com",
}
}
/>
}
id="url.bitbucket"
maxLength={2000}
onFieldChange={[MockFunction]}
propKey="url"
value=""
/>
<AlmBindingDefinitionFormField
id="personal_access_token"
isTextArea={true}
onFieldChange={[MockFunction]}
overwriteOnly={false}
propKey="personalAccessToken"
value=""
/>
</div>
</div>
`; `;

+ 25
- 67
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<div
className="bordered"
>
<AlmTab
alm="bitbucket"
branchesEnabled={true}
createConfiguration={[Function]}
defaultBinding={
<BitbucketTabRenderer
branchesEnabled={true}
definitionStatus={Object {}}
definitions={
Array [
Object { Object {
"key": "",
"personalAccessToken": "",
"url": "",
}
}
definitionStatus={Object {}}
definitions={
Array [
Object {
"key": "key",
"personalAccessToken": "asdf1234",
"url": "http://bbs.enterprise.com",
},
]
}
form={[Function]}
help={
<React.Fragment>
<h3>
onboarding.create_project.pat_help.title
</h3>
<p
className="big-spacer-top"
>
settings.almintegration.bitbucket.help_1
</p>
<ul
className="big-spacer-top list-styled"
>
<li>
settings.almintegration.bitbucket.help_2
</li>
<li>
settings.almintegration.bitbucket.help_3
</li>
</ul>
<p
className="big-spacer-top big-spacer-bottom"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/bitbucket-integration/"
>
learn_more
</Link>
</p>
</React.Fragment>
}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
/>
</div>
"key": "key",
"personalAccessToken": "asdf1234",
"url": "http://bbs.enterprise.com",
},
]
}
isCreating={false}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
onCancel={[Function]}
onCheck={[MockFunction]}
onCreate={[Function]}
onDelete={[MockFunction]}
onEdit={[Function]}
onSelectVariant={[Function]}
onSubmit={[Function]}
submitting={false}
success={false}
/>
`; `;

+ 143
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTabRenderer-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: bitbucket cloud 1`] = `
<div
className="bordered"
>
<AlmTabRenderer
alm="bitbucket"
branchesEnabled={true}
definitionStatus={Object {}}
definitions={Array []}
form={[Function]}
help={
<FormattedMessage
defaultMessage="settings.almintegration.bitbucketcloud.info"
id="settings.almintegration.bitbucketcloud.info"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/bitbucket-cloud-integration/"
>
learn_more
</Link>,
}
}
/>
}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
onCancel={[MockFunction]}
onCheck={[MockFunction]}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSubmit={[MockFunction]}
submitting={true}
success={false}
/>
</div>
`;

exports[`should render correctly: bitbucket form 1`] = `
<BitbucketForm
formData={
Object {
"key": "key",
"personalAccessToken": "asdf1234",
"url": "http://bbs.enterprise.com",
}
}
isCreating={false}
onFieldChange={[MockFunction]}
onSelectVariant={[MockFunction]}
/>
`;

exports[`should render correctly: bitbucket server 1`] = `
<div
className="bordered"
>
<AlmTabRenderer
alm="bitbucket"
branchesEnabled={true}
definitionStatus={Object {}}
definitions={Array []}
form={[Function]}
help={
<React.Fragment>
<h3>
onboarding.create_project.pat_help.title
</h3>
<p
className="big-spacer-top"
>
settings.almintegration.bitbucket.help_1
</p>
<ul
className="big-spacer-top list-styled"
>
<li>
settings.almintegration.bitbucket.help_2
</li>
<li>
settings.almintegration.bitbucket.help_3
</li>
</ul>
<p
className="big-spacer-top big-spacer-bottom"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/bitbucket-integration/"
>
learn_more
</Link>
</p>
</React.Fragment>
}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
onCancel={[MockFunction]}
onCheck={[MockFunction]}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSubmit={[MockFunction]}
submitting={true}
success={false}
/>
</div>
`;

exports[`should render correctly: default 1`] = `
<div
className="bordered"
>
<AlmTabRenderer
alm="bitbucket"
branchesEnabled={true}
definitionStatus={Object {}}
definitions={Array []}
form={[Function]}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
onCancel={[MockFunction]}
onCheck={[MockFunction]}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSubmit={[MockFunction]}
submitting={true}
success={false}
/>
</div>
`;

+ 4
- 4
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap View File

value="" value=""
/> />
<AlmBindingDefinitionFormField <AlmBindingDefinitionFormField
id="client_id"
id="client_id.github"
maxLength={80} maxLength={80}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="clientId" propKey="clientId"
value="" value=""
/> />
<AlmBindingDefinitionFormField <AlmBindingDefinitionFormField
id="client_secret"
id="client_secret.github"
maxLength={80} maxLength={80}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
overwriteOnly={false} overwriteOnly={false}
value="123456" value="123456"
/> />
<AlmBindingDefinitionFormField <AlmBindingDefinitionFormField
id="client_id"
id="client_id.github"
maxLength={80} maxLength={80}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="clientId" propKey="clientId"
value="client1" value="client1"
/> />
<AlmBindingDefinitionFormField <AlmBindingDefinitionFormField
id="client_secret"
id="client_secret.github"
maxLength={80} maxLength={80}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
overwriteOnly={true} overwriteOnly={true}

+ 7
- 1
server/sonar-web/src/main/js/apps/settings/components/almIntegration/utils.ts View File

*/ */
import { AlmKeys } from '../../../../types/alm-settings'; import { AlmKeys } from '../../../../types/alm-settings';


export const ALM_KEY_LIST = [AlmKeys.Azure, AlmKeys.Bitbucket, AlmKeys.GitHub, AlmKeys.GitLab];
export const ALM_KEY_LIST = [
AlmKeys.Azure,
AlmKeys.BitbucketServer,
AlmKeys.BitbucketCloud,
AlmKeys.GitHub,
AlmKeys.GitLab
];

+ 1
- 1
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx View File

{renderMonoRepoFieldWithDocLink(ALM_DOCUMENTATION_PATHS[AlmKeys.Azure])} {renderMonoRepoFieldWithDocLink(ALM_DOCUMENTATION_PATHS[AlmKeys.Azure])}
</> </>
); );
case AlmKeys.Bitbucket:
case AlmKeys.BitbucketServer:
return ( return (
<> <>
{renderField({ {renderField({

+ 3
- 2
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx View File

[almKey in AlmKeys]: Array<keyof T.Omit<FormData, 'key'>>; [almKey in AlmKeys]: Array<keyof T.Omit<FormData, 'key'>>;
} = { } = {
[AlmKeys.Azure]: ['repository', 'slug'], [AlmKeys.Azure]: ['repository', 'slug'],
[AlmKeys.Bitbucket]: ['repository', 'slug'],
[AlmKeys.BitbucketServer]: ['repository', 'slug'],
[AlmKeys.BitbucketCloud]: ['repository'],
[AlmKeys.GitHub]: ['repository'], [AlmKeys.GitHub]: ['repository'],
[AlmKeys.GitLab]: ['repository'] [AlmKeys.GitLab]: ['repository']
}; };
monorepo monorepo
}); });
} }
case AlmKeys.Bitbucket: {
case AlmKeys.BitbucketServer: {
if (!repository || !slug) { if (!repository || !slug) {
return Promise.reject(); return Promise.reject();
} }

+ 2
- 2
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx View File

it.each([ it.each([
[AlmKeys.Azure, false], [AlmKeys.Azure, false],
[AlmKeys.Azure, true], [AlmKeys.Azure, true],
[AlmKeys.Bitbucket, false],
[AlmKeys.Bitbucket, true],
[AlmKeys.BitbucketServer, false],
[AlmKeys.BitbucketServer, true],
[AlmKeys.GitHub, false], [AlmKeys.GitHub, false],
[AlmKeys.GitHub, true], [AlmKeys.GitHub, true],
[AlmKeys.GitLab, false], [AlmKeys.GitLab, false],

+ 5
- 5
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx View File

const instances: AlmSettingsInstance[] = [ const instances: AlmSettingsInstance[] = [
{ key: 'github', alm: AlmKeys.GitHub }, { key: 'github', alm: AlmKeys.GitHub },
{ key: 'azure', alm: AlmKeys.Azure }, { key: 'azure', alm: AlmKeys.Azure },
{ key: 'bitbucket', alm: AlmKeys.Bitbucket },
{ key: 'bitbucket', alm: AlmKeys.BitbucketServer },
{ key: 'gitlab', alm: AlmKeys.GitLab } { key: 'gitlab', alm: AlmKeys.GitLab }
]; ];


[AlmKeys.Azure, {}], [AlmKeys.Azure, {}],
[AlmKeys.Azure, { slug: 'test' }], [AlmKeys.Azure, { slug: 'test' }],
[AlmKeys.Azure, { repository: 'test' }], [AlmKeys.Azure, { repository: 'test' }],
[AlmKeys.Bitbucket, {}],
[AlmKeys.Bitbucket, { slug: 'test' }],
[AlmKeys.Bitbucket, { repository: 'test' }],
[AlmKeys.BitbucketServer, {}],
[AlmKeys.BitbucketServer, { slug: 'test' }],
[AlmKeys.BitbucketServer, { repository: 'test' }],
[AlmKeys.GitHub, {}], [AlmKeys.GitHub, {}],
[AlmKeys.GitLab, {}] [AlmKeys.GitLab, {}]
])('should properly reject promise for %s & %s', async (almKey: AlmKeys, params: {}) => { ])('should properly reject promise for %s & %s', async (almKey: AlmKeys, params: {}) => {
wrapper.setState({ wrapper.setState({
instances: [ instances: [
{ key: 'azure', alm: AlmKeys.Azure }, { key: 'azure', alm: AlmKeys.Azure },
{ key: 'bitbucket', alm: AlmKeys.Bitbucket },
{ key: 'bitbucket', alm: AlmKeys.BitbucketServer },
{ key: 'github', alm: AlmKeys.GitHub }, { key: 'github', alm: AlmKeys.GitHub },
{ key: 'gitlab', alm: AlmKeys.GitLab } { key: 'gitlab', alm: AlmKeys.GitLab }
] ]

+ 1
- 1
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx View File

url: urls[0] url: urls[0]
}, },
{ {
alm: AlmKeys.Bitbucket,
alm: AlmKeys.BitbucketServer,
key: 'i3', key: 'i3',
url: urls[1] url: urls[1]
}, },

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx View File

} }


const jenkinsAvailable = const jenkinsAvailable =
projectBinding && [AlmKeys.Bitbucket, AlmKeys.GitHub].includes(projectBinding.alm);
projectBinding && [AlmKeys.BitbucketServer, AlmKeys.GitHub].includes(projectBinding.alm);


return ( return (
<> <>

+ 3
- 3
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx View File

}); });


it('should not select anything if project is bound', async () => { it('should not select anything if project is bound', async () => {
(getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.Bitbucket });
(getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.BitbucketServer });
const wrapper = shallowRender(); const wrapper = shallowRender();
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);
expect(wrapper.state().forceManual).toBe(false); expect(wrapper.state().forceManual).toBe(false);
it('should correctly find the global ALM binding definition', async () => { it('should correctly find the global ALM binding definition', async () => {
const key = 'foo'; const key = 'foo';
const almBinding = mockBitbucketBindingDefinition({ key }); const almBinding = mockBitbucketBindingDefinition({ key });
(getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.Bitbucket, key });
(getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.BitbucketServer, key });
(getAlmDefinitionsNoCatch as jest.Mock).mockResolvedValueOnce({ (getAlmDefinitionsNoCatch as jest.Mock).mockResolvedValueOnce({
[AlmKeys.Bitbucket]: [almBinding]
[AlmKeys.BitbucketServer]: [almBinding]
}); });
const wrapper = shallowRender(); const wrapper = shallowRender();
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/jenkins/WebhookStep.tsx View File

const { almBinding, branchesEnabled, projectBinding } = props; const { almBinding, branchesEnabled, projectBinding } = props;


switch (projectBinding.alm) { switch (projectBinding.alm) {
case AlmKeys.Bitbucket:
case AlmKeys.BitbucketServer:
return ( return (
<WebhookStepBitbucket <WebhookStepBitbucket
almBinding={isBitbucketBindingDefinition(almBinding) ? almBinding : undefined} almBinding={isBitbucketBindingDefinition(almBinding) ? almBinding : undefined}

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/PreRequisitesStep-test.tsx View File

function shallowRender(props: Partial<PreRequisitesStepProps> = {}) { function shallowRender(props: Partial<PreRequisitesStepProps> = {}) {
return shallow<PreRequisitesStepProps>( return shallow<PreRequisitesStepProps>(
<PreRequisitesStep <PreRequisitesStep
alm={AlmKeys.Bitbucket}
alm={AlmKeys.BitbucketServer}
branchesEnabled={true} branchesEnabled={true}
onChangeSkipNextTime={jest.fn()} onChangeSkipNextTime={jest.fn()}
onDone={jest.fn()} onDone={jest.fn()}

+ 5
- 1
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/WebhookStep-test.tsx View File

mockAzureBindingDefinition(), mockAzureBindingDefinition(),
mockProjectAlmBindingResponse({ alm: AlmKeys.Azure }) mockProjectAlmBindingResponse({ alm: AlmKeys.Azure })
], ],
[AlmKeys.Bitbucket, mockBitbucketBindingDefinition(), mockProjectBitbucketBindingResponse()],
[
AlmKeys.BitbucketServer,
mockBitbucketBindingDefinition(),
mockProjectBitbucketBindingResponse()
],
[AlmKeys.GitHub, mockGithubBindingDefinition(), mockProjectGithubBindingResponse()], [AlmKeys.GitHub, mockGithubBindingDefinition(), mockProjectGithubBindingResponse()],
[ [
AlmKeys.GitLab, AlmKeys.GitLab,

+ 9
- 1
server/sonar-web/src/main/js/helpers/constants.ts View File



export const ALM_DOCUMENTATION_PATHS = { export const ALM_DOCUMENTATION_PATHS = {
[AlmKeys.Azure]: '/documentation/analysis/azuredevops-integration/', [AlmKeys.Azure]: '/documentation/analysis/azuredevops-integration/',
[AlmKeys.Bitbucket]: '/documentation/analysis/bitbucket-integration/',
[AlmKeys.BitbucketServer]: '/documentation/analysis/bitbucket-integration/',
[AlmKeys.BitbucketCloud]: '/documentation/analysis/bitbucket-cloud-integration/',
[AlmKeys.GitHub]: '/documentation/analysis/github-integration/', [AlmKeys.GitHub]: '/documentation/analysis/github-integration/',
[AlmKeys.GitLab]: '/documentation/analysis/gitlab-integration/' [AlmKeys.GitLab]: '/documentation/analysis/gitlab-integration/'
}; };

export const IMPORT_COMPATIBLE_ALMS = [
AlmKeys.Azure,
AlmKeys.BitbucketServer,
AlmKeys.GitHub,
AlmKeys.GitLab
];

+ 14
- 1
server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts View File

AlmSettingsInstance, AlmSettingsInstance,
AzureBindingDefinition, AzureBindingDefinition,
BitbucketBindingDefinition, BitbucketBindingDefinition,
BitbucketCloudBindingDefinition,
GithubBindingDefinition, GithubBindingDefinition,
GitlabBindingDefinition, GitlabBindingDefinition,
ProjectAlmBindingResponse, ProjectAlmBindingResponse,
}; };
} }


export function mockBitbucketCloudBindingDefinition(
overrides: Partial<BitbucketCloudBindingDefinition> = {}
): BitbucketCloudBindingDefinition {
return {
key: 'key',
clientId: 'client1',
clientSecret: '**clientsecret**',
workspace: 'workspace',
...overrides
};
}

export function mockGithubBindingDefinition( export function mockGithubBindingDefinition(
overrides: Partial<GithubBindingDefinition> = {} overrides: Partial<GithubBindingDefinition> = {}
): GithubBindingDefinition { ): GithubBindingDefinition {
overrides: Partial<ProjectBitbucketBindingResponse> = {} overrides: Partial<ProjectBitbucketBindingResponse> = {}
): ProjectBitbucketBindingResponse { ): ProjectBitbucketBindingResponse {
return { return {
alm: AlmKeys.Bitbucket,
alm: AlmKeys.BitbucketServer,
key: 'foo', key: 'foo',
repository: 'PROJECT_KEY', repository: 'PROJECT_KEY',
slug: 'repo-slug', slug: 'repo-slug',

+ 22
- 15
server/sonar-web/src/main/js/types/alm-settings.ts View File

*/ */
export const enum AlmKeys { export const enum AlmKeys {
Azure = 'azure', Azure = 'azure',
Bitbucket = 'bitbucket',
BitbucketServer = 'bitbucket',
BitbucketCloud = 'bitbucketcloud',
GitHub = 'github', GitHub = 'github',
GitLab = 'gitlab' GitLab = 'gitlab'
} }
url: string; url: string;
} }


export interface BitbucketCloudBindingDefinition extends AlmBindingDefinition {
clientId: string;
clientSecret: string;
workspace: string;
}

export interface GithubBindingDefinition extends AlmBindingDefinition { export interface GithubBindingDefinition extends AlmBindingDefinition {
appId: string; appId: string;
clientId: string; clientId: string;
} }


export interface ProjectBitbucketBindingResponse extends ProjectAlmBindingResponse { export interface ProjectBitbucketBindingResponse extends ProjectAlmBindingResponse {
alm: AlmKeys.Bitbucket;
alm: AlmKeys.BitbucketServer;
repository: string; repository: string;
slug: string; slug: string;
monorepo: boolean; monorepo: boolean;


export interface AlmSettingsBindingDefinitions { export interface AlmSettingsBindingDefinitions {
[AlmKeys.Azure]: AzureBindingDefinition[]; [AlmKeys.Azure]: AzureBindingDefinition[];
[AlmKeys.Bitbucket]: BitbucketBindingDefinition[];
[AlmKeys.BitbucketServer]: BitbucketBindingDefinition[];
[AlmKeys.BitbucketCloud]: BitbucketCloudBindingDefinition[];
[AlmKeys.GitHub]: GithubBindingDefinition[]; [AlmKeys.GitHub]: GithubBindingDefinition[];
[AlmKeys.GitLab]: GitlabBindingDefinition[]; [AlmKeys.GitLab]: GitlabBindingDefinition[];
} }
export function isProjectBitbucketBindingResponse( export function isProjectBitbucketBindingResponse(
binding: ProjectAlmBindingResponse binding: ProjectAlmBindingResponse
): binding is ProjectBitbucketBindingResponse { ): binding is ProjectBitbucketBindingResponse {
return binding.alm === AlmKeys.Bitbucket;
return binding.alm === AlmKeys.BitbucketServer;
} }


export function isProjectGitHubBindingResponse( export function isProjectGitHubBindingResponse(
} }


export function isBitbucketBindingDefinition( export function isBitbucketBindingDefinition(
binding?: AlmBindingDefinition & { url?: string; personalAccessToken?: string }
binding?: AlmBindingDefinition & { url?: string }
): binding is BitbucketBindingDefinition { ): binding is BitbucketBindingDefinition {
return (
binding !== undefined && binding.url !== undefined && binding.personalAccessToken !== undefined
);
return binding !== undefined && binding.url !== undefined;
}

export function isBitbucketCloudBindingDefinition(
binding?: AlmBindingDefinition & { clientId?: string; workspace?: string }
): binding is BitbucketCloudBindingDefinition {
return binding !== undefined && binding.clientId !== undefined && binding.workspace !== undefined;
} }


export function isGithubBindingDefinition( export function isGithubBindingDefinition(
binding?: AlmBindingDefinition & { appId?: string; privateKey?: string; url?: string }
binding?: AlmBindingDefinition & { appId?: string; url?: string }
): binding is GithubBindingDefinition { ): binding is GithubBindingDefinition {
return (
binding !== undefined &&
binding.appId !== undefined &&
binding.privateKey !== undefined &&
binding.url !== undefined
);
return binding !== undefined && binding.appId !== undefined && binding.url !== undefined;
} }

+ 14
- 5
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File



alm.azure=Azure DevOps alm.azure=Azure DevOps
alm.azure.short=Azure DevOps alm.azure.short=Azure DevOps
alm.bitbucket=Bitbucket Server
alm.bitbucket=Bitbucket
alm.bitbucket.short=Bitbucket alm.bitbucket.short=Bitbucket
alm.github=Github
alm.github.short=Github
alm.github=GitHub
alm.github.short=GitHub
alm.gitlab=GitLab alm.gitlab=GitLab
alm.gitlab.short=GitLab alm.gitlab.short=GitLab


settings.almintegration.bitbucket.help_1=SonarQube needs a Personal Access Token to communicate with Bitbucket Server. This token will be used to decorate Pull Requests. settings.almintegration.bitbucket.help_1=SonarQube needs a Personal Access Token to communicate with Bitbucket Server. This token will be used to decorate Pull Requests.
settings.almintegration.bitbucket.help_2=The account used for integration needs write permission. settings.almintegration.bitbucket.help_2=The account used for integration needs write permission.
settings.almintegration.bitbucket.help_3=We recommend to integrate with SonarQube using a Bitbucket Server Service Account. settings.almintegration.bitbucket.help_3=We recommend to integrate with SonarQube using a Bitbucket Server Service Account.
settings.almintegration.bitbucketcloud.info=You need to create an OAuth consumer in your Bitbucket Cloud workspace settings to decorate your Pull Requests. It needs to be a private consumer with Pull requests: Read permission. Bitbucket requires an OAuth callback URL, but it's not used by SonarQube so any URL works. {link}
settings.almintegration.empty.azure=Create your first Azure DevOps configuration to start analyzing your repositories on SonarQube. settings.almintegration.empty.azure=Create your first Azure DevOps configuration to start analyzing your repositories on SonarQube.
settings.almintegration.empty.bitbucket=Create your first Bitbucket configuration to start analyzing your repositories on SonarQube. settings.almintegration.empty.bitbucket=Create your first Bitbucket configuration to start analyzing your repositories on SonarQube.
settings.almintegration.empty.bitbucketcloud=Create your first Bitbucket Cloud configuration to start analyzing your repositories on SonarQube.
settings.almintegration.empty.github=Create your first GitHub configuration to start analyzing your repositories on SonarQube. settings.almintegration.empty.github=Create your first GitHub configuration to start analyzing your repositories on SonarQube.
settings.almintegration.empty.gitlab=Create your first GitLab configuration to start analyzing your repositories on SonarQube. settings.almintegration.empty.gitlab=Create your first GitLab configuration to start analyzing your repositories on SonarQube.
settings.almintegration.create=Create configuration settings.almintegration.create=Create configuration
settings.almintegration.form.second_instance_warning=Binding more than one instance of an ALM will deactivate the import of repositories from that ALM. settings.almintegration.form.second_instance_warning=Binding more than one instance of an ALM will deactivate the import of repositories from that ALM.
settings.almintegration.form.name.azure=Configuration name settings.almintegration.form.name.azure=Configuration name
settings.almintegration.form.name.azure.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured Azure instance for a project. settings.almintegration.form.name.azure.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured Azure instance for a project.
settings.almintegration.form.choose_bitbucket_variant=Select which variant you want to configure
settings.almintegration.form.name.bitbucket=Configuration name settings.almintegration.form.name.bitbucket=Configuration name
settings.almintegration.form.name.bitbucket.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured Bitbucket instance for a project. settings.almintegration.form.name.bitbucket.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured Bitbucket instance for a project.
settings.almintegration.form.name.bitbucketcloud=Configuration name
settings.almintegration.form.name.bitbucketcloud.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured Bitbucket Cloud instance for a project.
settings.almintegration.form.name.github=Configuration name settings.almintegration.form.name.github=Configuration name
settings.almintegration.form.name.github.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured GitHub App for a project. settings.almintegration.form.name.github.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured GitHub App for a project.
settings.almintegration.form.name.gitlab=Configuration name settings.almintegration.form.name.gitlab=Configuration name
settings.almintegration.form.name.gitlab.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured GitLab instance for a project. settings.almintegration.form.name.gitlab.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured GitLab instance for a project.
settings.almintegration.form.workspace.bitbucketcloud=Workspace ID
settings.almintegration.form.workspace.bitbucketcloud.help=The Workspace ID
settings.almintegration.form.url.azure=Azure DevOps URL settings.almintegration.form.url.azure=Azure DevOps URL
settings.almintegration.form.url.azure.help1=For Azure DevOps Server, provide the full collection URL: settings.almintegration.form.url.azure.help1=For Azure DevOps Server, provide the full collection URL:
settings.almintegration.form.url.azure.help2=For Azure DevOps Services, provide the full organization URL: settings.almintegration.form.url.azure.help2=For Azure DevOps Services, provide the full organization URL:
settings.almintegration.form.url.gitlab=GitLab API URL settings.almintegration.form.url.gitlab=GitLab API URL
settings.almintegration.form.url.gitlab.help=Provide the GitLab API URL. For example: settings.almintegration.form.url.gitlab.help=Provide the GitLab API URL. For example:
settings.almintegration.form.app_id=GitHub App ID settings.almintegration.form.app_id=GitHub App ID
settings.almintegration.form.client_id=GitHub Client ID
settings.almintegration.form.client_secret=GitHub Client Secret
settings.almintegration.form.client_id.github=Client ID
settings.almintegration.form.client_secret.github=Client Secret
settings.almintegration.form.client_id.bitbucketcloud=OAuth Key
settings.almintegration.form.client_secret.bitbucketcloud=OAuth Secret
settings.almintegration.form.private_key=Private Key settings.almintegration.form.private_key=Private Key
settings.almintegration.form.personal_access_token=Personal Access token settings.almintegration.form.personal_access_token=Personal Access token
settings.almintegration.form.personal_access_token.azure.help=Token of the user that will be used to decorate the Pull Requests. Needs authorized scope: "Code (read and write)". settings.almintegration.form.personal_access_token.azure.help=Token of the user that will be used to decorate the Pull Requests. Needs authorized scope: "Code (read and write)".

Loading…
Cancel
Save