]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14393 Add admin form for Bitbucket Cloud integration
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Tue, 26 Jan 2021 08:30:35 +0000 (09:30 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 8 Feb 2021 20:07:44 +0000 (20:07 +0000)
56 files changed:
server/sonar-web/src/main/js/api/alm-settings.ts
server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx
server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreate-test.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreateRenderer-test.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/PersonalAccessTokenForm-test.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/WrongBindingCountAlert-test.tsx
server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenu-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCreationMenuItem-test.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormModalRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketForm.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTabRenderer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionBox-test.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketForm-test.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTabRenderer-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormModalRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketForm-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTabRenderer-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/utils.ts
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx
server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/WebhookStep.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/PreRequisitesStep-test.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/WebhookStep-test.tsx
server/sonar-web/src/main/js/helpers/constants.ts
server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts
server/sonar-web/src/main/js/types/alm-settings.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 26953bca8a079618147edd109346890ee29b302b..4c4f6430ad1ecc1c939d8aaf8fb30b96301dc1ca 100644 (file)
@@ -25,6 +25,7 @@ import {
   AzureBindingDefinition,
   AzureProjectAlmBindingParams,
   BitbucketBindingDefinition,
+  BitbucketCloudBindingDefinition,
   BitbucketProjectAlmBindingParams,
   GithubBindingDefinition,
   GithubProjectAlmBindingParams,
@@ -87,6 +88,16 @@ export function updateBitbucketConfiguration(
   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) {
   return post('/api/alm_settings/create_gitlab', data).catch(throwGlobalError);
 }
index a256fc3202e6326e4361ef05ae9b538ec42c1694..0f71610155bd38139e86214df219f268bdc20af0 100644 (file)
@@ -101,7 +101,7 @@ export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCr
       {loading && <i className="spinner" />}
 
       {!loading && !bitbucketSetting && (
-        <WrongBindingCountAlert alm={AlmKeys.Bitbucket} canAdmin={!!canAdmin} />
+        <WrongBindingCountAlert alm={AlmKeys.BitbucketServer} canAdmin={!!canAdmin} />
       )}
 
       {!loading &&
index 136fb393e5f4fec0e4d62411a567936a838252ad..e14ba1730495841aef5709f1284f1768e02f00e7 100644 (file)
@@ -29,7 +29,12 @@ import { ALM_INTEGRATION } from '../../settings/components/AdditionalCategoryKey
 import { CreateProjectModes } from './types';
 
 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'>;
   loadingBindings: boolean;
   onSelectMode: (mode: CreateProjectModes) => void;
@@ -37,7 +42,7 @@ export interface CreateProjectModeSelectionProps {
 
 function renderAlmOption(
   props: CreateProjectModeSelectionProps,
-  alm: AlmKeys,
+  alm: AlmKeys.Azure | AlmKeys.BitbucketServer | AlmKeys.GitHub | AlmKeys.GitLab,
   mode: CreateProjectModes
 ) {
   const {
@@ -145,7 +150,7 @@ export function CreateProjectModeSelection(props: CreateProjectModeSelectionProp
         </button>
 
         {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.GitLab, CreateProjectModes.GitLab)}
       </div>
index c2bdfd909e2a86c4884f6faa3905f22070c41935..45feaadb2ad80838cf9a88868ad05c663cf4abae 100644 (file)
@@ -75,7 +75,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> {
         if (this.mounted) {
           this.setState({
             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),
             gitlabSettings: almSettings.filter(s => s.alm === AlmKeys.GitLab),
             loading: false
@@ -171,7 +171,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> {
       default: {
         const almCounts = {
           [AlmKeys.Azure]: azureSettings.length,
-          [AlmKeys.Bitbucket]: bitbucketSettings.length,
+          [AlmKeys.BitbucketServer]: bitbucketSettings.length,
           [AlmKeys.GitHub]: githubSettings.length,
           [AlmKeys.GitLab]: gitlabSettings.length
         };
index 9062a7e84acb18f3eb63ffb3388979aca597623a..51811a40ac1b729b20752a3257b97ad42a37e42b 100644 (file)
@@ -36,7 +36,7 @@ export interface PersonalAccessTokenFormProps {
 }
 
 function getPatUrl(alm: AlmKeys, url: string) {
-  if (alm === AlmKeys.Bitbucket) {
+  if (alm === AlmKeys.BitbucketServer) {
     return `${url.replace(/\/$/, '')}/plugins/servlet/access-tokens/add`;
   } else {
     // GitLab
@@ -132,7 +132,7 @@ export default function PersonalAccessTokenForm(props: PersonalAccessTokenFormPr
         </p>
 
         <ul>
-          {alm === AlmKeys.Bitbucket && (
+          {alm === AlmKeys.BitbucketServer && (
             <>
               <li>
                 <FormattedMessage
index 43f47357a905d4f0fc0f1670943cec8f35f775d3..e1317528df62b2aa07a245be1f40c3dad49edb82 100644 (file)
@@ -163,7 +163,7 @@ function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) {
   return shallow<BitbucketProjectCreate>(
     <BitbucketProjectCreate
       canAdmin={false}
-      bitbucketSettings={[mockAlmSettingsInstance({ alm: AlmKeys.Bitbucket, key: 'foo' })]}
+      bitbucketSettings={[mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer, key: 'foo' })]}
       loadingBindings={false}
       location={mockLocation()}
       onProjectCreate={jest.fn()}
index 2d8990bba1e1e1036dd9a8c4602a6c791d56f5ec..127da7584f45e7c8075b03399e3d7d95948de329 100644 (file)
@@ -48,7 +48,7 @@ it('should render correctly', () => {
 function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {}) {
   return shallow<BitbucketProjectCreateRendererProps>(
     <BitbucketProjectCreateRenderer
-      bitbucketSetting={mockAlmSettingsInstance({ alm: AlmKeys.Bitbucket })}
+      bitbucketSetting={mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer })}
       importing={false}
       loading={false}
       onImportRepository={jest.fn()}
index 642a92aba77f01142c13fdfc55cc191c84f3ec2b..33dfbd452158eb68537c28aed75a2e1f26d37dc7 100644 (file)
@@ -30,11 +30,14 @@ import { CreateProjectModes } from '../types';
 it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot('default');
   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'
   );
   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');
 });
 
@@ -71,7 +74,7 @@ function shallowRender(
 ) {
   const almCounts = {
     [AlmKeys.Azure]: 0,
-    [AlmKeys.Bitbucket]: 1,
+    [AlmKeys.BitbucketServer]: 1,
     [AlmKeys.GitHub]: 0,
     [AlmKeys.GitLab]: 0,
     ...almCountOverrides
index e59baf2bb09fb413da3b93d3b9dcc05c6964447f..dd9682364f3941765a1af5cad39e56e39ae7c5c4 100644 (file)
@@ -26,7 +26,7 @@ import { CreateProjectPage } from '../CreateProjectPage';
 import { CreateProjectModes } from '../types';
 
 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);
index 2f4ee698f96ad181848dd6e16bcf211368cc8fae..5e7571a211bd9d6fe712e3e7b5ea7df72ddef78d 100644 (file)
@@ -71,7 +71,7 @@ function shallowRender(props: Partial<PersonalAccessTokenFormProps> = {}) {
   return shallow<PersonalAccessTokenFormProps>(
     <PersonalAccessTokenForm
       almSetting={mockAlmSettingsInstance({
-        alm: AlmKeys.Bitbucket,
+        alm: AlmKeys.BitbucketServer,
         url: 'http://www.example.com'
       })}
       onPersonalAccessTokenCreate={jest.fn()}
index cc15f4311f05701565596f15676e4e3bd67400b2..3cc603af89caf505f2f555e2be0f161e2d7b1e95 100644 (file)
@@ -24,10 +24,12 @@ import WrongBindingCountAlert, { WrongBindingCountAlertProps } from '../WrongBin
 
 it('should render correctly', () => {
   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');
 });
 
 function shallowRender(props: Partial<WrongBindingCountAlertProps> = {}) {
-  return shallow(<WrongBindingCountAlert alm={AlmKeys.Bitbucket} canAdmin={false} {...props} />);
+  return shallow(
+    <WrongBindingCountAlert alm={AlmKeys.BitbucketServer} canAdmin={false} {...props} />
+  );
 }
index 4a406a6a36f46a652876991cfc9b62793842a575..fb282ba3f49a716e7192401e230d1b75480bec78 100644 (file)
@@ -24,6 +24,7 @@ import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon';
 import { translate } from 'sonar-ui-common/helpers/l10n';
 import { getAlmSettings } from '../../../api/alm-settings';
 import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
+import { IMPORT_COMPATIBLE_ALMS } from '../../../helpers/constants';
 import { hasGlobalPermission } from '../../../helpers/users';
 import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
 import ProjectCreationMenuItem from './ProjectCreationMenuItem';
@@ -38,14 +39,11 @@ interface State {
 }
 
 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 = {
   [AlmKeys.Azure]: (settings: AlmSettingsInstance) => !!settings.url,
-  [AlmKeys.Bitbucket]: (_: AlmSettingsInstance) => true,
+  [AlmKeys.BitbucketServer]: (_: AlmSettingsInstance) => true,
+  [AlmKeys.BitbucketCloud]: (_: AlmSettingsInstance) => false,
   [AlmKeys.GitHub]: (_: AlmSettingsInstance) => true,
   [AlmKeys.GitLab]: (settings: AlmSettingsInstance) => !!settings.url
 };
index 5b2825de4b2132470aea0eeb94450740232277b5..90e7b675473f54c129ba24e84bd2ef343ebe3e08 100644 (file)
@@ -55,8 +55,8 @@ it('should not fetch alm bindings if user cannot create projects', async () => {
 it('should filter alm bindings appropriately', async () => {
   (getAlmSettings as jest.Mock).mockResolvedValueOnce([
     { 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.GitLab, url: 'gitlab.com' }
   ]);
index 65325429a39243fa417837f47086c29f34e74515..2825b03ba1f39467d37eb0a153ec7ef22d932f83 100644 (file)
@@ -28,5 +28,5 @@ it('should render correctly', () => {
 });
 
 function shallowRender(overrides: Partial<ProjectCreationMenuItemProps> = {}) {
-  return shallow(<ProjectCreationMenuItem alm={AlmKeys.Bitbucket} {...overrides} />);
+  return shallow(<ProjectCreationMenuItem alm={AlmKeys.BitbucketServer} {...overrides} />);
 }
index c83889511c9a5ba0edbf46a9fc945e6caf6e7877..9f1d81371f9da0a400d9e35c41801540fa71710c 100644 (file)
@@ -29,6 +29,7 @@ import EditIcon from 'sonar-ui-common/components/icons/EditIcon';
 import { Alert } from 'sonar-ui-common/components/ui/Alert';
 import { translate } from 'sonar-ui-common/helpers/l10n';
 import { getEdition, getEditionUrl } from '../../../../helpers/editions';
+import { IMPORT_COMPATIBLE_ALMS } from '../../../../helpers/constants';
 import {
   AlmBindingDefinition,
   AlmKeys,
@@ -140,12 +141,12 @@ function getImportFeatureStatus(
 export default function AlmBindingDefinitionBox(props: AlmBindingDefinitionBoxProps) {
   const { alm, branchesEnabled, definition, multipleDefinitions, status = DEFAULT_STATUS } = props;
 
-  const importFeatureTitle =
+  const prDecoFeatureTitle =
     alm === AlmKeys.GitLab
       ? translate('settings.almintegration.feature.mr_decoration.title')
       : translate('settings.almintegration.feature.pr_decoration.title');
 
-  const importFeatureDescription =
+  const prDecoFeatureDescription =
     alm === AlmKeys.GitLab
       ? translate('settings.almintegration.feature.mr_decoration.description')
       : translate('settings.almintegration.feature.pr_decoration.description');
@@ -178,20 +179,24 @@ export default function AlmBindingDefinitionBox(props: AlmBindingDefinitionBoxPr
           {status.type !== AlmSettingsBindingStatusType.Warning && (
             <div className="display-flex-row spacer-bottom">
               <div className="huge-spacer-right">
-                <Tooltip overlay={importFeatureDescription}>
-                  <span>{importFeatureTitle}</span>
+                <Tooltip overlay={prDecoFeatureDescription}>
+                  <span>{prDecoFeatureTitle}</span>
                 </Tooltip>
                 {getPRDecorationFeatureStatus(branchesEnabled, status.type)}
               </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>
           )}
 
index 2c0ac91f58d3ea94c09474417c4dfc893a8192a6..c3600b8bfe85fe37003fa0a918ce4c8daa599812 100644 (file)
@@ -58,10 +58,12 @@ export default function AlmBindingDefinitionFormModalRenderer(
             <div className="display-flex-start">
               <div className="flex-1">{children}</div>
 
-              {help && (
+              {help ? (
                 <Alert className="huge-spacer-left flex-1" variant="info">
                   {help}
                 </Alert>
+              ) : (
+                <div className="flex-1" />
               )}
             </div>
           </div>
index 254cffa1d76e70cd33ac019505f25bf3edc3baf6..7845f393fb3c85d1f89f06e8d3e67d25ee15f34e 100644 (file)
@@ -62,7 +62,8 @@ export class AlmIntegration extends React.PureComponent<Props, State> {
       currentAlm: props.location.query.alm || AlmKeys.GitHub,
       definitions: {
         [AlmKeys.Azure]: [],
-        [AlmKeys.Bitbucket]: [],
+        [AlmKeys.BitbucketServer]: [],
+        [AlmKeys.BitbucketCloud]: [],
         [AlmKeys.GitHub]: [],
         [AlmKeys.GitLab]: []
       },
index 2583b5c064e607ab4f77ef2af522d1735c9da1fc..92ced87796c69f98238856d8fb609c1a169338d7 100644 (file)
@@ -68,7 +68,7 @@ const tabs = [
     requiresBranchesEnabled: false
   },
   {
-    key: AlmKeys.Bitbucket,
+    key: AlmKeys.BitbucketServer,
     label: (
       <>
         <img
@@ -77,7 +77,7 @@ const tabs = [
           height={16}
           src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
         />
-        Bitbucket Server
+        Bitbucket
       </>
     ),
     requiresBranchesEnabled: false
@@ -156,10 +156,10 @@ export default function AlmIntegrationRenderer(props: AlmIntegrationRendererProp
           onUpdateDefinitions={props.onUpdateDefinitions}
         />
       )}
-      {currentAlm === AlmKeys.Bitbucket && (
+      {currentAlm === AlmKeys.BitbucketServer && (
         <BitbucketTab
           branchesEnabled={branchesEnabled}
-          definitions={definitions.bitbucket}
+          definitions={[...definitions.bitbucket, ...definitions.bitbucketcloud]}
           definitionStatus={definitionStatus}
           loadingAlmDefinitions={loadingAlmDefinitions}
           loadingProjectCount={loadingProjectCount}
index 95c680e8c5da0d411ba93fa215a3ef5169143d74..53a845726959ffd5198f79c145e4edd6a5c08d24 100644 (file)
@@ -67,10 +67,7 @@ export default class AlmTab<B extends AlmBindingDefinition> extends React.PureCo
   }
 
   handleCancel = () => {
-    this.setState({
-      editedDefinition: undefined,
-      success: false
-    });
+    this.setState({ editedDefinition: undefined, success: false });
   };
 
   handleCreate = () => {
@@ -93,7 +90,11 @@ export default class AlmTab<B extends AlmBindingDefinition> extends React.PureCo
     return call
       .then(() => {
         if (this.mounted) {
-          this.setState({ editedDefinition: undefined, submitting: false, success: true });
+          this.setState({
+            editedDefinition: undefined,
+            submitting: false,
+            success: true
+          });
         }
       })
       .then(this.props.onUpdateDefinitions)
index c7c1cbd5a7f5357f8f6990c5d4b9d0980aa34483..e0f13c920db4bbd2a320ce6ad1eca894e35602b4 100644 (file)
@@ -24,7 +24,8 @@ import { translate } from 'sonar-ui-common/helpers/l10n';
 import {
   AlmBindingDefinition,
   AlmKeys,
-  AlmSettingsBindingStatus
+  AlmSettingsBindingStatus,
+  isBitbucketCloudBindingDefinition
 } from '../../../../types/alm-settings';
 import AlmBindingDefinitionBox from './AlmBindingDefinitionBox';
 import AlmBindingDefinitionForm, {
@@ -93,7 +94,7 @@ export default function AlmTabRenderer<B extends AlmBindingDefinition>(
         </div>
         {definitions.map(def => (
           <AlmBindingDefinitionBox
-            alm={alm}
+            alm={isBitbucketCloudBindingDefinition(def) ? AlmKeys.BitbucketCloud : alm}
             branchesEnabled={branchesEnabled}
             definition={def}
             key={def.key}
index 504222cbdca2de4763a9be1e5ddcca1ce96e7e5c..15bd95265a8f6ef8e3f53ff0ba088d02159adcd6 100644 (file)
  */
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import RadioToggle from 'sonar-ui-common/components/controls/RadioToggle';
 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';
 
 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) {
-  const { formData, hideKeyField, onFieldChange, readOnly } = props;
+  const { formData, isCreating, variant } = props;
 
   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>
   );
 }
index 1ea3352965b797b69385583b3ba7bfaccd1b76be..df05024ece79cbf223ad901f23c50126210fff96 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { Link } from 'react-router';
-import { translate } from 'sonar-ui-common/helpers/l10n';
 import {
+  createBitbucketCloudConfiguration,
   createBitbucketConfiguration,
+  updateBitbucketCloudConfiguration,
   updateBitbucketConfiguration
 } from '../../../../api/alm-settings';
-import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
 import {
   AlmKeys,
   AlmSettingsBindingStatus,
-  BitbucketBindingDefinition
+  BitbucketBindingDefinition,
+  BitbucketCloudBindingDefinition,
+  isBitbucketBindingDefinition
 } from '../../../../types/alm-settings';
-import AlmTab from './AlmTab';
-import BitbucketForm from './BitbucketForm';
+import BitbucketTabRenderer from './BitbucketTabRenderer';
 
-export interface BitbucketTabProps {
+interface Props {
   branchesEnabled: boolean;
-  definitions: BitbucketBindingDefinition[];
+  definitions: Array<BitbucketBindingDefinition | BitbucketCloudBindingDefinition>;
   definitionStatus: T.Dict<AlmSettingsBindingStatus>;
   loadingAlmDefinitions: boolean;
   loadingProjectCount: boolean;
@@ -45,54 +45,142 @@ export interface BitbucketTabProps {
   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}
-        createConfiguration={createBitbucketConfiguration}
-        defaultBinding={{ key: '', url: '', personalAccessToken: '' }}
         definitions={definitions}
         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}
         loadingProjectCount={loadingProjectCount}
         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>
-  );
+    );
+  }
 }
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTabRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTabRenderer.tsx
new file mode 100644 (file)
index 0000000..e586f2b
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * 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>
+  );
+}
index fe097ae0d60ea14284bc90e4ed591171c905c70f..87cb47d8652f43914676762c872f1e161b609bd4 100644 (file)
@@ -74,7 +74,7 @@ export default function GithubForm(props: GithubFormProps) {
         value={formData.appId}
       />
       <AlmBindingDefinitionFormField
-        id="client_id"
+        id="client_id.github"
         maxLength={80}
         onFieldChange={onFieldChange}
         propKey="clientId"
@@ -82,7 +82,7 @@ export default function GithubForm(props: GithubFormProps) {
         value={formData.clientId}
       />
       <AlmBindingDefinitionFormField
-        id="client_secret"
+        id="client_secret.github"
         maxLength={80}
         onFieldChange={onFieldChange}
         overwriteOnly={Boolean(formData.key)}
index f122eb43c8f3574bd9b6c171dfb08a63a02d4cfd..a7dac59645b7cc844ea27a16ca1a53e5f216f071 100644 (file)
@@ -22,7 +22,9 @@ import * as React from 'react';
 import {
   mockAlmSettingsBindingStatus,
   mockAzureBindingDefinition,
-  mockGithubBindingDefinition
+  mockBitbucketCloudBindingDefinition,
+  mockGithubBindingDefinition,
+  mockGitlabBindingDefinition
 } from '../../../../../helpers/mocks/alm-settings';
 import { AlmKeys, AlmSettingsBindingStatusType } from '../../../../../types/alm-settings';
 import AlmBindingDefinitionBox, { AlmBindingDefinitionBoxProps } from '../AlmBindingDefinitionBox';
@@ -67,6 +69,26 @@ it('should render correctly', () => {
     shallowRender({ alm: AlmKeys.Azure, definition: mockAzureBindingDefinition() })
   ).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(
     shallowRender({
       branchesEnabled: false,
index fe7e883bf6aa0f0ab690709513a4b073f33e7eec..59d81720e26f29918e6f37c67408f4b29d589ffe 100644 (file)
@@ -35,7 +35,7 @@ jest.mock('../../../../../api/alm-settings', () => ({
   deleteConfiguration: jest.fn().mockResolvedValue(undefined),
   getAlmDefinitions: jest
     .fn()
-    .mockResolvedValue({ azure: [], bitbucket: [], github: [], gitlab: [] }),
+    .mockResolvedValue({ azure: [], bitbucket: [], bitbucketcloud: [], github: [], gitlab: [] }),
   validateAlmSettings: jest.fn().mockResolvedValue('')
 }));
 
@@ -50,7 +50,8 @@ it('should render correctly', () => {
 it('should validate existing configurations', async () => {
   (getAlmDefinitions as jest.Mock).mockResolvedValueOnce({
     [AlmKeys.Azure]: [{ key: 'a1' }],
-    [AlmKeys.Bitbucket]: [{ key: 'b1' }],
+    [AlmKeys.BitbucketServer]: [{ key: 'b1' }],
+    [AlmKeys.BitbucketCloud]: [{ key: 'bc1' }],
     [AlmKeys.GitHub]: [{ key: 'gh1' }, { key: 'gh2' }],
     [AlmKeys.GitLab]: [{ key: 'gl1' }]
   });
@@ -59,9 +60,10 @@ it('should validate existing configurations', async () => {
 
   await waitAndUpdate(wrapper);
 
-  expect(validateAlmSettings).toBeCalledTimes(5);
+  expect(validateAlmSettings).toBeCalledTimes(6);
   expect(validateAlmSettings).toBeCalledWith('a1');
   expect(validateAlmSettings).toBeCalledWith('b1');
+  expect(validateAlmSettings).toBeCalledWith('bc1');
   expect(validateAlmSettings).toBeCalledWith('gh1');
   expect(validateAlmSettings).toBeCalledWith('gh2');
   expect(validateAlmSettings).toBeCalledWith('gl1');
@@ -111,6 +113,7 @@ it('should validate a configuration', async () => {
   (validateAlmSettings as jest.Mock)
     .mockRejectedValueOnce(undefined)
     .mockResolvedValueOnce(failureMessage)
+    .mockResolvedValueOnce('')
     .mockResolvedValueOnce('');
 
   await wrapper.instance().handleCheck(definitionKey);
@@ -141,7 +144,8 @@ it('should validate a configuration', async () => {
 it('should fetch settings', async () => {
   const definitions = {
     [AlmKeys.Azure]: [{ key: 'a1' }],
-    [AlmKeys.Bitbucket]: [{ key: 'b1' }],
+    [AlmKeys.BitbucketServer]: [{ key: 'b1' }],
+    [AlmKeys.BitbucketCloud]: [{ key: 'bc1' }],
     [AlmKeys.GitHub]: [{ key: 'gh1' }],
     [AlmKeys.GitLab]: [{ key: 'gl1' }]
   };
index cc4a11a494210ab37d100b72d45ecca524be8a2c..6b33832d728042e6fa35ac392f7049940fd38528 100644 (file)
@@ -31,7 +31,8 @@ it('should render correctly', () => {
     'delete modal'
   );
   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');
 });
 
@@ -40,7 +41,7 @@ function shallowRender(props: Partial<AlmIntegrationRendererProps> = {}) {
     <AlmIntegrationRenderer
       branchesEnabled={true}
       currentAlm={AlmKeys.GitHub}
-      definitions={{ azure: [], bitbucket: [], github: [], gitlab: [] }}
+      definitions={{ azure: [], bitbucket: [], bitbucketcloud: [], github: [], gitlab: [] }}
       definitionStatus={{}}
       loadingAlmDefinitions={false}
       loadingProjectCount={false}
index 8e1f243485f171e8d58ae4acbfe5a30e6e2c0af0..242e6293cc3bd5d7ba2aa99dc8b71e1cc8698fcb 100644 (file)
@@ -21,6 +21,7 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import {
   mockAzureBindingDefinition,
+  mockBitbucketCloudBindingDefinition,
   mockGithubBindingDefinition
 } from '../../../../../helpers/mocks/alm-settings';
 import {
@@ -60,7 +61,7 @@ it('should render correctly with validation', () => {
     defaultBinding: mockGithubBindingDefinition(),
     definitions: [mockGithubBindingDefinition()]
   };
-  expect(shallowRender(githubProps)).toMatchSnapshot();
+  expect(shallowRender(githubProps)).toMatchSnapshot('default');
   expect(shallowRender({ ...githubProps, definitions: [] })).toMatchSnapshot('empty');
 
   expect(
@@ -77,6 +78,13 @@ it('should render correctly with validation', () => {
       editedDefinition: mockGithubBindingDefinition()
     })
   ).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>> = {}) {
index 8c758f307b9c378ce985aff8bed18497d4a5405a..daa8f83f4979e7f49682a8c5301d3a6fb86ee737 100644 (file)
  */
 import { shallow } from 'enzyme';
 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';
 
 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> = {}) {
   return shallow(
     <BitbucketForm
       formData={{ key: '', personalAccessToken: '', url: '' }}
+      isCreating={false}
       onFieldChange={jest.fn()}
+      onSelectVariant={jest.fn()}
+      variant={AlmKeys.BitbucketServer}
       {...props}
     />
   );
index 28551069145c19621968b87150f38aae6859fa67..5a95099ac5f1fbe85ed073b7f2321216ce0d2705 100644 (file)
  */
 import { shallow } from 'enzyme';
 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', () => {
   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
       branchesEnabled={true}
       definitions={[mockBitbucketBindingDefinition()]}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTabRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTabRenderer-test.tsx
new file mode 100644 (file)
index 0000000..09ac75c
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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}
+    />
+  );
+}
index 07cd7a44076e0c0cabb580401d9c9da4803f9cbc..4fce50260a749097e45fd3d83eeaaf783c100bea 100644 (file)
@@ -283,6 +283,152 @@ exports[`should render correctly: success 1`] = `
 </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`] = `
 <div
   className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
index 211f66c346ca7b97a3e9d3906878899d8019c159..3cf445a2f4643513d72bc683a5ddc80c3d20e845 100644 (file)
@@ -28,6 +28,9 @@ exports[`should render correctly 1`] = `
         >
           <Component />
         </div>
+        <div
+          className="flex-1"
+        />
       </div>
     </div>
     <div
@@ -86,6 +89,9 @@ exports[`should render correctly: second instance 1`] = `
         >
           <Component />
         </div>
+        <div
+          className="flex-1"
+        />
       </div>
     </div>
     <div
index 647758269cb6db6c41a948f6f4f91fdd01483cab..2494c7dca2084d34c46ca9f7d840df66d54f1d7c 100644 (file)
@@ -9,6 +9,7 @@ exports[`should render correctly 1`] = `
     Object {
       "azure": Array [],
       "bitbucket": Array [],
+      "bitbucketcloud": Array [],
       "github": Array [],
       "gitlab": Array [],
     }
index a7b63393329044e01e69d216fdfef5c4489bf781..14df1c80a7a12d83c159b1ab3edcb5fbc6c6099b 100644 (file)
@@ -43,7 +43,7 @@ exports[`should render correctly: azure 1`] = `
               height={16}
               src="/images/alm/bitbucket.svg"
             />
-            Bitbucket Server
+            Bitbucket
           </React.Fragment>,
           "requiresBranchesEnabled": false,
         },
@@ -133,7 +133,7 @@ exports[`should render correctly: bitbucket 1`] = `
               height={16}
               src="/images/alm/bitbucket.svg"
             />
-            Bitbucket Server
+            Bitbucket
           </React.Fragment>,
           "requiresBranchesEnabled": false,
         },
@@ -180,6 +180,85 @@ exports[`should render correctly: bitbucket 1`] = `
 </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`] = `
 <Fragment>
   <header
@@ -223,7 +302,7 @@ exports[`should render correctly: default 1`] = `
               height={16}
               src="/images/alm/bitbucket.svg"
             />
-            Bitbucket Server
+            Bitbucket
           </React.Fragment>,
           "requiresBranchesEnabled": false,
         },
@@ -313,7 +392,7 @@ exports[`should render correctly: delete modal 1`] = `
               height={16}
               src="/images/alm/bitbucket.svg"
             />
-            Bitbucket Server
+            Bitbucket
           </React.Fragment>,
           "requiresBranchesEnabled": false,
         },
@@ -408,7 +487,7 @@ exports[`should render correctly: gitlab 1`] = `
               height={16}
               src="/images/alm/bitbucket.svg"
             />
-            Bitbucket Server
+            Bitbucket
           </React.Fragment>,
           "requiresBranchesEnabled": false,
         },
@@ -498,7 +577,7 @@ exports[`should render correctly: loading 1`] = `
               height={16}
               src="/images/alm/bitbucket.svg"
             />
-            Bitbucket Server
+            Bitbucket
           </React.Fragment>,
           "requiresBranchesEnabled": false,
         },
index 76720ff1334d15ea4f3a66c59c753c8f51a340b0..9822401deaf55479dffd0c30832d4441305f453a 100644 (file)
@@ -4,13 +4,6 @@ exports[`should render correctly 1`] = `
 <AlmTabRenderer
   alm="azure"
   branchesEnabled={true}
-  defaultBinding={
-    Object {
-      "key": "",
-      "personalAccessToken": "",
-      "url": undefined,
-    }
-  }
   definitionStatus={Object {}}
   definitions={
     Array [
index 816690de7ed74e5651a60420c399d657119247ff..f0ff2da8675519341e0116f54d5f794fdc5e3e80 100644 (file)
@@ -350,15 +350,20 @@ exports[`should render correctly for single-ALM binding 3`] = `
 </div>
 `;
 
-exports[`should render correctly with validation 1`] = `
+exports[`should render correctly with validation: create a first 1`] = `
 <div
   className="big-padded"
 >
   <DeferredSpinner
     loading={false}
   >
+    <p
+      className="spacer-top"
+    >
+      settings.almintegration.empty.github
+    </p>
     <div
-      className="spacer-bottom text-right"
+      className="big-spacer-top"
     >
       <Connect(withAppState(CreationTooltip))
         alm="github"
@@ -373,10 +378,8 @@ exports[`should render correctly with validation 1`] = `
         </Button>
       </Connect(withAppState(CreationTooltip))>
     </div>
-    <AlmBindingDefinitionBox
-      alm="github"
-      branchesEnabled={true}
-      definition={
+    <AlmBindingDefinitionForm
+      bindingDefinition={
         Object {
           "appId": "123456",
           "clientId": "client1",
@@ -386,30 +389,26 @@ exports[`should render correctly with validation 1`] = `
           "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>
 </div>
 `;
 
-exports[`should render correctly with validation: create a first 1`] = `
+exports[`should render correctly with validation: create a second 1`] = `
 <div
   className="big-padded"
 >
   <DeferredSpinner
     loading={false}
   >
-    <p
-      className="spacer-top"
-    >
-      settings.almintegration.empty.github
-    </p>
     <div
-      className="big-spacer-top"
+      className="spacer-bottom text-right"
     >
       <Connect(withAppState(CreationTooltip))
         alm="github"
@@ -424,6 +423,25 @@ exports[`should render correctly with validation: create a first 1`] = `
         </Button>
       </Connect(withAppState(CreationTooltip))>
     </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
       bindingDefinition={
         Object {
@@ -436,7 +454,7 @@ exports[`should render correctly with validation: create a first 1`] = `
         }
       }
       help={<div />}
-      isSecondInstance={false}
+      isSecondInstance={true}
       onCancel={[MockFunction]}
       onSubmit={[MockFunction]}
     >
@@ -446,7 +464,7 @@ exports[`should render correctly with validation: create a first 1`] = `
 </div>
 `;
 
-exports[`should render correctly with validation: create a second 1`] = `
+exports[`should render correctly with validation: default 1`] = `
 <div
   className="big-padded"
 >
@@ -488,24 +506,6 @@ exports[`should render correctly with validation: create a second 1`] = `
       onDelete={[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>
 </div>
 `;
@@ -541,3 +541,47 @@ exports[`should render correctly with validation: empty 1`] = `
   </DeferredSpinner>
 </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>
+`;
index e19b6f4af80f437bd0bd0c6dcb8cf71e87831d79..be3c262eca18479634cd5febf141cfcf8c437185 100644 (file)
 // 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>
 `;
index e7b2a23b1da3a2d9d077b13c570986cdf5ad0b8f..8100d78442d30f836e38030f74a56984f0a5649e 100644 (file)
@@ -1,72 +1,30 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should render correctly 1`] = `
-<div
-  className="bordered"
->
-  <AlmTab
-    alm="bitbucket"
-    branchesEnabled={true}
-    createConfiguration={[Function]}
-    defaultBinding={
+<BitbucketTabRenderer
+  branchesEnabled={true}
+  definitionStatus={Object {}}
+  definitions={
+    Array [
       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}
+/>
 `;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTabRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTabRenderer-test.tsx.snap
new file mode 100644 (file)
index 0000000..e742c9c
--- /dev/null
@@ -0,0 +1,143 @@
+// 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>
+`;
index 6ae40e3ac21edda82bc1960fac8a8e3ab5362ec4..79359f8817a76e570a4fa33e5fd76c8c32d06860 100644 (file)
@@ -41,14 +41,14 @@ exports[`should render correctly 1`] = `
     value=""
   />
   <AlmBindingDefinitionFormField
-    id="client_id"
+    id="client_id.github"
     maxLength={80}
     onFieldChange={[MockFunction]}
     propKey="clientId"
     value=""
   />
   <AlmBindingDefinitionFormField
-    id="client_secret"
+    id="client_secret.github"
     maxLength={80}
     onFieldChange={[MockFunction]}
     overwriteOnly={false}
@@ -107,14 +107,14 @@ exports[`should render correctly 2`] = `
     value="123456"
   />
   <AlmBindingDefinitionFormField
-    id="client_id"
+    id="client_id.github"
     maxLength={80}
     onFieldChange={[MockFunction]}
     propKey="clientId"
     value="client1"
   />
   <AlmBindingDefinitionFormField
-    id="client_secret"
+    id="client_secret.github"
     maxLength={80}
     onFieldChange={[MockFunction]}
     overwriteOnly={true}
index 4dbd16c1c271e26f326b48ba980e9f9f06c8a762..5e118230c4b32db419fea89ecc818e4bf20da40d 100644 (file)
  */
 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
+];
index 69ecd2f9e36315674108f5ffb941db635dde73e9..52a985f6fb00bfe72fb408b05d975c0f67105af1 100644 (file)
@@ -182,7 +182,7 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) {
           {renderMonoRepoFieldWithDocLink(ALM_DOCUMENTATION_PATHS[AlmKeys.Azure])}
         </>
       );
-    case AlmKeys.Bitbucket:
+    case AlmKeys.BitbucketServer:
       return (
         <>
           {renderField({
index 3d9a226923e5788a0474681681fb46506dec9422..4f4d34a1564713649dcd36f771d2cab6b6510947 100644 (file)
@@ -65,7 +65,8 @@ const REQUIRED_FIELDS_BY_ALM: {
   [almKey in AlmKeys]: Array<keyof T.Omit<FormData, 'key'>>;
 } = {
   [AlmKeys.Azure]: ['repository', 'slug'],
-  [AlmKeys.Bitbucket]: ['repository', 'slug'],
+  [AlmKeys.BitbucketServer]: ['repository', 'slug'],
+  [AlmKeys.BitbucketCloud]: ['repository'],
   [AlmKeys.GitHub]: ['repository'],
   [AlmKeys.GitLab]: ['repository']
 };
@@ -180,7 +181,7 @@ export class PRDecorationBinding extends React.PureComponent<Props & StateProps,
           monorepo
         });
       }
-      case AlmKeys.Bitbucket: {
+      case AlmKeys.BitbucketServer: {
         if (!repository || !slug) {
           return Promise.reject();
         }
index 38c13dc03cdb740c63fe72f1b279cb807ca1b6f0..1edea77f342fe9fc0d6df48f6fd8751edbcb0aae 100644 (file)
@@ -25,8 +25,8 @@ import AlmSpecificForm, { AlmSpecificFormProps } from '../AlmSpecificForm';
 it.each([
   [AlmKeys.Azure, false],
   [AlmKeys.Azure, true],
-  [AlmKeys.Bitbucket, false],
-  [AlmKeys.Bitbucket, true],
+  [AlmKeys.BitbucketServer, false],
+  [AlmKeys.BitbucketServer, true],
   [AlmKeys.GitHub, false],
   [AlmKeys.GitHub, true],
   [AlmKeys.GitLab, false],
index 6d9c7e4dd6548da9fcbc2753225b92fb5e266ad4..b5b4fd22e70657f13bfa130f39d27c6ec4e2db45 100644 (file)
@@ -93,7 +93,7 @@ describe('handleSubmit', () => {
   const instances: AlmSettingsInstance[] = [
     { key: 'github', alm: AlmKeys.GitHub },
     { key: 'azure', alm: AlmKeys.Azure },
-    { key: 'bitbucket', alm: AlmKeys.Bitbucket },
+    { key: 'bitbucket', alm: AlmKeys.BitbucketServer },
     { key: 'gitlab', alm: AlmKeys.GitLab }
   ];
 
@@ -263,9 +263,9 @@ it.each([
   [AlmKeys.Azure, {}],
   [AlmKeys.Azure, { slug: '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.GitLab, {}]
 ])('should properly reject promise for %s & %s', async (almKey: AlmKeys, params: {}) => {
@@ -289,7 +289,7 @@ it('should validate form', async () => {
   wrapper.setState({
     instances: [
       { key: 'azure', alm: AlmKeys.Azure },
-      { key: 'bitbucket', alm: AlmKeys.Bitbucket },
+      { key: 'bitbucket', alm: AlmKeys.BitbucketServer },
       { key: 'github', alm: AlmKeys.GitHub },
       { key: 'gitlab', alm: AlmKeys.GitLab }
     ]
index 9bb9c158a99b4dd090bb6723b8982ac380dbfe4c..b0520110cf07221939f274f9fb27e85d32257799 100644 (file)
@@ -59,7 +59,7 @@ it('should render multiple instances correctly', () => {
       url: urls[0]
     },
     {
-      alm: AlmKeys.Bitbucket,
+      alm: AlmKeys.BitbucketServer,
       key: 'i3',
       url: urls[1]
     },
index 3c4cc3ac579626b6624fa75971bdd89abadc7213..40674894fd561eaa22d633ef2cf8976348934420 100644 (file)
@@ -45,7 +45,7 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
   }
 
   const jenkinsAvailable =
-    projectBinding && [AlmKeys.Bitbucket, AlmKeys.GitHub].includes(projectBinding.alm);
+    projectBinding && [AlmKeys.BitbucketServer, AlmKeys.GitHub].includes(projectBinding.alm);
 
   return (
     <>
index 5c593a74ac8ec211271cc4507a4be04b42ba35b5..76b4dbe9d79e4f5d206ff53660c1bb583d80dacf 100644 (file)
@@ -50,7 +50,7 @@ it('should select manual if project is not 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();
   await waitAndUpdate(wrapper);
   expect(wrapper.state().forceManual).toBe(false);
@@ -59,9 +59,9 @@ it('should not select anything if project is bound', async () => {
 it('should correctly find the global ALM binding definition', async () => {
   const key = 'foo';
   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({
-    [AlmKeys.Bitbucket]: [almBinding]
+    [AlmKeys.BitbucketServer]: [almBinding]
   });
   const wrapper = shallowRender();
   await waitAndUpdate(wrapper);
index a337323434676bdbc7f6e0e8ab7afffee39b9f4e..2349c1f90e8f6d660cbe9e5cc273f1bf5e77ef95 100644 (file)
@@ -46,7 +46,7 @@ function renderAlmSpecificInstructions(props: WebhookStepProps) {
   const { almBinding, branchesEnabled, projectBinding } = props;
 
   switch (projectBinding.alm) {
-    case AlmKeys.Bitbucket:
+    case AlmKeys.BitbucketServer:
       return (
         <WebhookStepBitbucket
           almBinding={isBitbucketBindingDefinition(almBinding) ? almBinding : undefined}
index c2ffc748a20b6a8393d2fdc4b6795544adf1658e..89d5cbf9bb028fc94db4cd1dcd8d5d2fd9c41379 100644 (file)
@@ -36,7 +36,7 @@ it('should render correctly', () => {
 function shallowRender(props: Partial<PreRequisitesStepProps> = {}) {
   return shallow<PreRequisitesStepProps>(
     <PreRequisitesStep
-      alm={AlmKeys.Bitbucket}
+      alm={AlmKeys.BitbucketServer}
       branchesEnabled={true}
       onChangeSkipNextTime={jest.fn()}
       onDone={jest.fn()}
index b7479be8a46e264fae2498c864c835fe9c9c16c7..e08fa92d0c73f3353f6e7502bcfd787a70992e77 100644 (file)
@@ -38,7 +38,11 @@ it.each([
     mockAzureBindingDefinition(),
     mockProjectAlmBindingResponse({ alm: AlmKeys.Azure })
   ],
-  [AlmKeys.Bitbucket, mockBitbucketBindingDefinition(), mockProjectBitbucketBindingResponse()],
+  [
+    AlmKeys.BitbucketServer,
+    mockBitbucketBindingDefinition(),
+    mockProjectBitbucketBindingResponse()
+  ],
   [AlmKeys.GitHub, mockGithubBindingDefinition(), mockProjectGithubBindingResponse()],
   [
     AlmKeys.GitLab,
index e7bfcfa1e2574b94fb823ad58875038f29d04899..5cfc1f6a4adf5473b584384f852f6dc29f996345 100644 (file)
@@ -65,7 +65,15 @@ export const PROJECT_KEY_MAX_LEN = 400;
 
 export const ALM_DOCUMENTATION_PATHS = {
   [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.GitLab]: '/documentation/analysis/gitlab-integration/'
 };
+
+export const IMPORT_COMPATIBLE_ALMS = [
+  AlmKeys.Azure,
+  AlmKeys.BitbucketServer,
+  AlmKeys.GitHub,
+  AlmKeys.GitLab
+];
index 5e48a2c263c08d69eebbca67b7368b0872fc0c0a..cff3a5b4ff4cf2eddc606ebdab852cb5f18e8922 100644 (file)
@@ -24,6 +24,7 @@ import {
   AlmSettingsInstance,
   AzureBindingDefinition,
   BitbucketBindingDefinition,
+  BitbucketCloudBindingDefinition,
   GithubBindingDefinition,
   GitlabBindingDefinition,
   ProjectAlmBindingResponse,
@@ -64,6 +65,18 @@ export function mockBitbucketBindingDefinition(
   };
 }
 
+export function mockBitbucketCloudBindingDefinition(
+  overrides: Partial<BitbucketCloudBindingDefinition> = {}
+): BitbucketCloudBindingDefinition {
+  return {
+    key: 'key',
+    clientId: 'client1',
+    clientSecret: '**clientsecret**',
+    workspace: 'workspace',
+    ...overrides
+  };
+}
+
 export function mockGithubBindingDefinition(
   overrides: Partial<GithubBindingDefinition> = {}
 ): GithubBindingDefinition {
@@ -102,7 +115,7 @@ export function mockProjectBitbucketBindingResponse(
   overrides: Partial<ProjectBitbucketBindingResponse> = {}
 ): ProjectBitbucketBindingResponse {
   return {
-    alm: AlmKeys.Bitbucket,
+    alm: AlmKeys.BitbucketServer,
     key: 'foo',
     repository: 'PROJECT_KEY',
     slug: 'repo-slug',
index ce3c5a0cb90dc7ec0e5c1c4f82ae64da185dfa63..0e81f6d70a3e4516d98d02e6b467684f0cdc6da2 100644 (file)
@@ -19,7 +19,8 @@
  */
 export const enum AlmKeys {
   Azure = 'azure',
-  Bitbucket = 'bitbucket',
+  BitbucketServer = 'bitbucket',
+  BitbucketCloud = 'bitbucketcloud',
   GitHub = 'github',
   GitLab = 'gitlab'
 }
@@ -39,6 +40,12 @@ export interface BitbucketBindingDefinition extends AlmBindingDefinition {
   url: string;
 }
 
+export interface BitbucketCloudBindingDefinition extends AlmBindingDefinition {
+  clientId: string;
+  clientSecret: string;
+  workspace: string;
+}
+
 export interface GithubBindingDefinition extends AlmBindingDefinition {
   appId: string;
   clientId: string;
@@ -70,7 +77,7 @@ export interface ProjectAzureBindingResponse extends ProjectAlmBindingResponse {
 }
 
 export interface ProjectBitbucketBindingResponse extends ProjectAlmBindingResponse {
-  alm: AlmKeys.Bitbucket;
+  alm: AlmKeys.BitbucketServer;
   repository: string;
   slug: string;
   monorepo: boolean;
@@ -125,7 +132,8 @@ export interface AlmSettingsInstance {
 
 export interface AlmSettingsBindingDefinitions {
   [AlmKeys.Azure]: AzureBindingDefinition[];
-  [AlmKeys.Bitbucket]: BitbucketBindingDefinition[];
+  [AlmKeys.BitbucketServer]: BitbucketBindingDefinition[];
+  [AlmKeys.BitbucketCloud]: BitbucketCloudBindingDefinition[];
   [AlmKeys.GitHub]: GithubBindingDefinition[];
   [AlmKeys.GitLab]: GitlabBindingDefinition[];
 }
@@ -146,7 +154,7 @@ export enum AlmSettingsBindingStatusType {
 export function isProjectBitbucketBindingResponse(
   binding: ProjectAlmBindingResponse
 ): binding is ProjectBitbucketBindingResponse {
-  return binding.alm === AlmKeys.Bitbucket;
+  return binding.alm === AlmKeys.BitbucketServer;
 }
 
 export function isProjectGitHubBindingResponse(
@@ -168,20 +176,19 @@ export function isProjectAzureBindingResponse(
 }
 
 export function isBitbucketBindingDefinition(
-  binding?: AlmBindingDefinition & { url?: string; personalAccessToken?: string }
+  binding?: AlmBindingDefinition & { url?: string }
 ): 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(
-  binding?: AlmBindingDefinition & { appId?: string; privateKey?: string; url?: string }
+  binding?: AlmBindingDefinition & { appId?: string; url?: string }
 ): binding is GithubBindingDefinition {
-  return (
-    binding !== undefined &&
-    binding.appId !== undefined &&
-    binding.privateKey !== undefined &&
-    binding.url !== undefined
-  );
+  return binding !== undefined && binding.appId !== undefined && binding.url !== undefined;
 }
index 3aff37f6d0a21702488c2523384a7698819e7699..779baa23258a5029dbc4bb4e8c9b4ef9790c6341 100644 (file)
@@ -361,10 +361,10 @@ Sa=Sa
 
 alm.azure=Azure DevOps
 alm.azure.short=Azure DevOps
-alm.bitbucket=Bitbucket Server
+alm.bitbucket=Bitbucket
 alm.bitbucket.short=Bitbucket
-alm.github=Github
-alm.github.short=Github
+alm.github=GitHub
+alm.github.short=GitHub
 alm.gitlab=GitLab
 alm.gitlab.short=GitLab
 
@@ -1075,8 +1075,10 @@ settings.almintegration.gitlab.info=Accounts that will be used to decorate Merge
 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_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.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.gitlab=Create your first GitLab configuration to start analyzing your repositories on SonarQube.
 settings.almintegration.create=Create configuration
@@ -1095,12 +1097,17 @@ settings.almintegration.form.header.edit=Edit the 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.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.choose_bitbucket_variant=Select which variant you want to configure
 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.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.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.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.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:
@@ -1112,8 +1119,10 @@ settings.almintegration.form.url.github.help2=If using GitHub.com:
 settings.almintegration.form.url.gitlab=GitLab API URL
 settings.almintegration.form.url.gitlab.help=Provide the GitLab API URL. For example:
 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.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)".