]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17588 Allow project onboarding when multiple Bitbucket integrations are configured
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>
Thu, 17 Nov 2022 14:59:16 +0000 (15:59 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 22 Nov 2022 20:03:04 +0000 (20:03 +0000)
28 files changed:
server/sonar-web/src/main/js/apps/create/project/AlmSettingsInstanceDropdown.tsx
server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx
server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreateRender.tsx
server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx
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/__tests__/BitbucketCloudProjectCreate-test.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloudProjectCreateRender-test.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__/__snapshots__/BitbucketCloudProjectCreate-test.tsx.snap
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudProjectCreateRender-test.tsx.snap
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreate-test.tsx.snap
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectModeSelection-test.tsx.snap
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
server/sonar-web/src/main/js/apps/create/project/constants.ts
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/settings/components/almIntegration/AlmBindingDefinitionBox.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.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__/__snapshots__/AlmBindingDefinitionBox-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/types/alm-settings.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 1bd3d726f48446df76afd3b6c74bdf12db5f5747..6a6776fb3555d5d9f08f4883ec4418f018703c66 100644 (file)
@@ -28,24 +28,25 @@ export interface AlmSettingsInstanceDropdownProps {
   onChangeConfig: (instance: AlmSettingsInstance) => void;
 }
 
+const MIN_SIZE_INSTANCES = 2;
+
 export default function AlmSettingsInstanceDropdown(props: AlmSettingsInstanceDropdownProps) {
   const { almInstances, selectedAlmInstance } = props;
+  if (!almInstances || almInstances.length < MIN_SIZE_INSTANCES) {
+    return null;
+  }
   return (
-    <>
-      {almInstances && almInstances.length > 1 ? (
-        <div className="display-flex-column huge-spacer-bottom">
-          <label htmlFor="alm-config-selector" className="spacer-bottom">
-            {translate('alm.configuration.selector.label')}
-          </label>
-          <AlmSettingsInstanceSelector
-            instances={almInstances}
-            onChange={props.onChangeConfig}
-            initialValue={selectedAlmInstance ? selectedAlmInstance.key : undefined}
-            classNames="abs-width-400"
-            inputId="alm-config-selector"
-          />
-        </div>
-      ) : null}
-    </>
+    <div className="display-flex-column huge-spacer-bottom">
+      <label htmlFor="alm-config-selector" className="spacer-bottom">
+        {translate('alm.configuration.selector.label')}
+      </label>
+      <AlmSettingsInstanceSelector
+        instances={almInstances}
+        onChange={props.onChangeConfig}
+        initialValue={selectedAlmInstance ? selectedAlmInstance.key : undefined}
+        classNames="abs-width-400"
+        inputId="alm-config-selector"
+      />
+    </div>
   );
 }
index a091375ccadd1a34bc13bff14ad757260aee69d4..0ee1f087eb41037f574be5bdaaf009d78d28dde5 100644 (file)
@@ -30,7 +30,7 @@ import BitbucketCloudProjectCreateRenderer from './BitbucketCloudProjectCreateRe
 
 interface Props {
   canAdmin: boolean;
-  settings: AlmSettingsInstance[];
+  almInstances: AlmSettingsInstance[];
   loadingBindings: boolean;
   onProjectCreate: (projectKey: string) => void;
   location: Location;
@@ -47,7 +47,7 @@ interface State {
   repositories: BitbucketCloudRepository[];
   searching: boolean;
   searchQuery: string;
-  settings: AlmSettingsInstance;
+  selectedAlmInstance: AlmSettingsInstance;
   showPersonalAccessTokenForm: boolean;
 }
 
@@ -67,7 +67,7 @@ export default class BitbucketCloudProjectCreate extends React.PureComponent<Pro
       repositories: [],
       searching: false,
       searchQuery: '',
-      settings: props.settings[0],
+      selectedAlmInstance: props.almInstances[0],
       showPersonalAccessTokenForm: true,
     };
   }
@@ -77,8 +77,8 @@ export default class BitbucketCloudProjectCreate extends React.PureComponent<Pro
   }
 
   componentDidUpdate(prevProps: Props) {
-    if (prevProps.settings.length === 0 && this.props.settings.length > 0) {
-      this.setState({ settings: this.props.settings[0] }, () => this.fetchData());
+    if (prevProps.almInstances.length === 0 && this.props.almInstances.length > 0) {
+      this.setState({ selectedAlmInstance: this.props.almInstances[0] }, () => this.fetchData());
     }
   }
 
@@ -98,14 +98,14 @@ export default class BitbucketCloudProjectCreate extends React.PureComponent<Pro
 
   async fetchData(more = false) {
     const {
-      settings,
+      selectedAlmInstance,
       searchQuery,
       projectsPaging: { pageIndex, pageSize },
       showPersonalAccessTokenForm,
     } = this.state;
-    if (settings && !showPersonalAccessTokenForm) {
+    if (selectedAlmInstance && !showPersonalAccessTokenForm) {
       const { isLastPage, repositories } = await searchForBitbucketCloudRepositories(
-        settings.key,
+        selectedAlmInstance.key,
         searchQuery,
         pageSize,
         pageIndex
@@ -174,17 +174,18 @@ export default class BitbucketCloudProjectCreate extends React.PureComponent<Pro
   };
 
   handleImport = async (repositorySlug: string) => {
-    const { settings } = this.state;
+    const { selectedAlmInstance } = this.state;
 
-    if (!settings) {
+    if (!selectedAlmInstance) {
       return;
     }
 
     this.setState({ importingSlug: repositorySlug });
 
-    const result = await importBitbucketCloudRepository(settings.key, repositorySlug).catch(
-      () => undefined
-    );
+    const result = await importBitbucketCloudRepository(
+      selectedAlmInstance.key,
+      repositorySlug
+    ).catch(() => undefined);
 
     if (this.mounted) {
       this.setState({ importingSlug: undefined });
@@ -195,12 +196,23 @@ export default class BitbucketCloudProjectCreate extends React.PureComponent<Pro
     }
   };
 
+  onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => {
+    this.setState({
+      selectedAlmInstance: instance,
+      showPersonalAccessTokenForm: true,
+      resetPat: false,
+      searching: false,
+      searchQuery: '',
+      projectsPaging: { pageIndex: 1, pageSize: BITBUCKET_CLOUD_PROJECTS_PAGESIZE },
+    });
+  };
+
   render() {
-    const { canAdmin, loadingBindings, location } = this.props;
+    const { canAdmin, loadingBindings, location, almInstances } = this.props;
     const {
       importingSlug,
       isLastPage = true,
-      settings,
+      selectedAlmInstance,
       loading,
       loadingMore,
       repositories,
@@ -213,7 +225,8 @@ export default class BitbucketCloudProjectCreate extends React.PureComponent<Pro
       <BitbucketCloudProjectCreateRenderer
         importingSlug={importingSlug}
         isLastPage={isLastPage}
-        settings={settings}
+        selectedAlmInstance={selectedAlmInstance}
+        almInstances={almInstances}
         canAdmin={canAdmin}
         loadingMore={loadingMore}
         loading={loading || loadingBindings}
@@ -221,6 +234,7 @@ export default class BitbucketCloudProjectCreate extends React.PureComponent<Pro
         onLoadMore={this.handleLoadMore}
         onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated}
         onSearch={this.handleSearch}
+        onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange}
         repositories={repositories}
         searching={searching}
         searchQuery={searchQuery}
index 359b1e65014dc358b997d81c63035a71948ce9b9..514a8db6df61aefe5f212c1dc9eaf511ed199824 100644 (file)
@@ -22,6 +22,7 @@ import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/system';
 import { BitbucketCloudRepository } from '../../../types/alm-integration';
 import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
+import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown';
 import BitbucketCloudSearchForm from './BitbucketCloudSearchForm';
 import CreateProjectPageHeader from './CreateProjectPageHeader';
 import PersonalAccessTokenForm from './PersonalAccessTokenForm';
@@ -37,21 +38,24 @@ export interface BitbucketCloudProjectCreateRendererProps {
   onLoadMore: () => void;
   onPersonalAccessTokenCreated: () => void;
   onSearch: (searchQuery: string) => void;
+  onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void;
   repositories?: BitbucketCloudRepository[];
   resetPat: boolean;
   searching: boolean;
   searchQuery: string;
   showPersonalAccessTokenForm: boolean;
-  settings?: AlmSettingsInstance;
+  almInstances: AlmSettingsInstance[];
+  selectedAlmInstance?: AlmSettingsInstance;
 }
 
 export default function BitbucketCloudProjectCreateRenderer(
   props: BitbucketCloudProjectCreateRendererProps
 ) {
   const {
+    almInstances,
     importingSlug,
     isLastPage,
-    settings,
+    selectedAlmInstance,
     canAdmin,
     loading,
     loadingMore,
@@ -77,17 +81,24 @@ export default function BitbucketCloudProjectCreateRenderer(
           </span>
         }
       />
+
+      <AlmSettingsInstanceDropdown
+        almInstances={almInstances}
+        selectedAlmInstance={selectedAlmInstance}
+        onChangeConfig={props.onSelectedAlmInstanceChange}
+      />
+
       {loading && <i className="spinner" />}
 
-      {!loading && !settings && (
+      {!loading && !selectedAlmInstance && (
         <WrongBindingCountAlert alm={AlmKeys.BitbucketCloud} canAdmin={!!canAdmin} />
       )}
 
       {!loading &&
-        settings &&
+        selectedAlmInstance &&
         (showPersonalAccessTokenForm ? (
           <PersonalAccessTokenForm
-            almSetting={settings}
+            almSetting={selectedAlmInstance}
             resetPat={resetPat}
             onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated}
           />
index 97b08aeed0d4d9af271bd3e13dffbe6e01d84a92..92ad18e876b4d5348c5f0eba8179ab4950989110 100644 (file)
@@ -36,7 +36,7 @@ import { DEFAULT_BBS_PAGE_SIZE } from './constants';
 
 interface Props {
   canAdmin: boolean;
-  bitbucketSettings: AlmSettingsInstance[];
+  almInstances: AlmSettingsInstance[];
   loadingBindings: boolean;
   onProjectCreate: (projectKey: string) => void;
   location: Location;
@@ -44,7 +44,7 @@ interface Props {
 }
 
 interface State {
-  bitbucketSetting?: AlmSettingsInstance;
+  selectedAlmInstance?: AlmSettingsInstance;
   importing: boolean;
   loading: boolean;
   projects?: BitbucketProject[];
@@ -63,7 +63,7 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S
     this.state = {
       // For now, we only handle a single instance. So we always use the first
       // one from the list.
-      bitbucketSetting: props.bitbucketSettings[0],
+      selectedAlmInstance: props.almInstances[0],
       importing: false,
       loading: false,
       searching: false,
@@ -76,8 +76,8 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S
   }
 
   componentDidUpdate(prevProps: Props) {
-    if (prevProps.bitbucketSettings.length === 0 && this.props.bitbucketSettings.length > 0) {
-      this.setState({ bitbucketSetting: this.props.bitbucketSettings[0] }, () =>
+    if (prevProps.almInstances.length === 0 && this.props.almInstances.length > 0) {
+      this.setState({ selectedAlmInstance: this.props.almInstances[0] }, () =>
         this.fetchInitialData()
       );
     }
@@ -112,27 +112,27 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S
   };
 
   fetchBitbucketProjects = (): Promise<BitbucketProject[] | undefined> => {
-    const { bitbucketSetting } = this.state;
+    const { selectedAlmInstance } = this.state;
 
-    if (!bitbucketSetting) {
+    if (!selectedAlmInstance) {
       return Promise.resolve(undefined);
     }
 
-    return getBitbucketServerProjects(bitbucketSetting.key).then(({ projects }) => projects);
+    return getBitbucketServerProjects(selectedAlmInstance.key).then(({ projects }) => projects);
   };
 
   fetchBitbucketRepositories = (
     projects: BitbucketProject[]
   ): Promise<BitbucketProjectRepositories | undefined> => {
-    const { bitbucketSetting } = this.state;
+    const { selectedAlmInstance } = this.state;
 
-    if (!bitbucketSetting) {
+    if (!selectedAlmInstance) {
       return Promise.resolve(undefined);
     }
 
     return Promise.all(
       projects.map((p) => {
-        return getBitbucketServerRepositories(bitbucketSetting.key, p.name).then(
+        return getBitbucketServerRepositories(selectedAlmInstance.key, p.name).then(
           ({ isLastPage, repositories }) => {
             // Because the WS uses the project name rather than its key to find
             // repositories, we can match more repositories than we expect. For
@@ -183,15 +183,15 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S
   };
 
   handleImportRepository = () => {
-    const { bitbucketSetting, selectedRepository } = this.state;
+    const { selectedAlmInstance, selectedRepository } = this.state;
 
-    if (!bitbucketSetting || !selectedRepository) {
+    if (!selectedAlmInstance || !selectedRepository) {
       return;
     }
 
     this.setState({ importing: true });
     importBitbucketServerProject(
-      bitbucketSetting.key,
+      selectedAlmInstance.key,
       selectedRepository.projectKey,
       selectedRepository.slug
     )
@@ -209,9 +209,9 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S
   };
 
   handleSearch = (query: string) => {
-    const { bitbucketSetting } = this.state;
+    const { selectedAlmInstance } = this.state;
 
-    if (!bitbucketSetting) {
+    if (!selectedAlmInstance) {
       return;
     }
 
@@ -221,7 +221,7 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S
     }
 
     this.setState({ searching: true, selectedRepository: undefined });
-    searchForBitbucketServerRepositories(bitbucketSetting.key, query)
+    searchForBitbucketServerRepositories(selectedAlmInstance.key, query)
       .then(({ repositories }) => {
         if (this.mounted) {
           this.setState({ searching: false, searchResults: repositories });
@@ -238,10 +238,19 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S
     this.setState({ selectedRepository });
   };
 
+  onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => {
+    this.setState({
+      selectedAlmInstance: instance,
+      showPersonalAccessTokenForm: true,
+      searching: false,
+      searchResults: undefined,
+    });
+  };
+
   render() {
-    const { canAdmin, loadingBindings, location } = this.props;
+    const { canAdmin, loadingBindings, location, almInstances } = this.props;
     const {
-      bitbucketSetting,
+      selectedAlmInstance,
       importing,
       loading,
       projectRepositories,
@@ -254,7 +263,8 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S
 
     return (
       <BitbucketCreateProjectRenderer
-        bitbucketSetting={bitbucketSetting}
+        selectedAlmInstance={selectedAlmInstance}
+        almInstances={almInstances}
         canAdmin={canAdmin}
         importing={importing}
         loading={loading || loadingBindings}
@@ -262,6 +272,7 @@ export default class BitbucketProjectCreate extends React.PureComponent<Props, S
         onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated}
         onSearch={this.handleSearch}
         onSelectRepository={this.handleSelectRepository}
+        onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange}
         projectRepositories={projectRepositories}
         projects={projects}
         resetPat={Boolean(location.query.resetPat)}
index 4c001740ea665764033eea221b8229763a063e2b..4a0f98f84ba799f50914c25c2ad068289694f11a 100644 (file)
@@ -28,13 +28,15 @@ import {
   BitbucketRepository,
 } from '../../../types/alm-integration';
 import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
+import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown';
 import BitbucketImportRepositoryForm from './BitbucketImportRepositoryForm';
 import CreateProjectPageHeader from './CreateProjectPageHeader';
 import PersonalAccessTokenForm from './PersonalAccessTokenForm';
 import WrongBindingCountAlert from './WrongBindingCountAlert';
 
 export interface BitbucketProjectCreateRendererProps {
-  bitbucketSetting?: AlmSettingsInstance;
+  selectedAlmInstance?: AlmSettingsInstance;
+  almInstances: AlmSettingsInstance[];
   canAdmin?: boolean;
   importing: boolean;
   loading: boolean;
@@ -42,6 +44,7 @@ export interface BitbucketProjectCreateRendererProps {
   onSearch: (query: string) => void;
   onSelectRepository: (repo: BitbucketRepository) => void;
   onPersonalAccessTokenCreated: () => void;
+  onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void;
   projects?: BitbucketProject[];
   projectRepositories?: BitbucketProjectRepositories;
   resetPat: boolean;
@@ -53,7 +56,8 @@ export interface BitbucketProjectCreateRendererProps {
 
 export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCreateRendererProps) {
   const {
-    bitbucketSetting,
+    almInstances,
+    selectedAlmInstance,
     canAdmin,
     importing,
     loading,
@@ -96,17 +100,23 @@ export default function BitbucketProjectCreateRenderer(props: BitbucketProjectCr
         }
       />
 
+      <AlmSettingsInstanceDropdown
+        almInstances={almInstances}
+        selectedAlmInstance={selectedAlmInstance}
+        onChangeConfig={props.onSelectedAlmInstanceChange}
+      />
+
       {loading && <i className="spinner" />}
 
-      {!loading && !bitbucketSetting && (
+      {!loading && !selectedAlmInstance && (
         <WrongBindingCountAlert alm={AlmKeys.BitbucketServer} canAdmin={!!canAdmin} />
       )}
 
       {!loading &&
-        bitbucketSetting &&
+        selectedAlmInstance &&
         (showPersonalAccessTokenForm ? (
           <PersonalAccessTokenForm
-            almSetting={bitbucketSetting}
+            almSetting={selectedAlmInstance}
             onPersonalAccessTokenCreated={props.onPersonalAccessTokenCreated}
             resetPat={resetPat}
           />
index 22f27994a94276ece4565d59327b03c6ad60b8bf..ac16e767c1dcf4adea6f9cc11d07366e4a8ae307 100644 (file)
@@ -24,11 +24,10 @@ import classNames from 'classnames';
 import * as React from 'react';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
 import ChevronsIcon from '../../../components/icons/ChevronsIcon';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/system';
-import { AlmKeys } from '../../../types/alm-settings';
+import { AlmKeys, AlmKeysUnion } from '../../../types/alm-settings';
 import { AppState } from '../../../types/appstate';
-import { ALLOWED_MULTIPLE_CONFIGS } from './constants';
 import { CreateProjectModes } from './types';
 
 export interface CreateProjectModeSelectionProps {
@@ -43,35 +42,18 @@ export interface CreateProjectModeSelectionProps {
 
 const DEFAULT_ICON_SIZE = 50;
 
-function getErrorMessage(
-  hasTooManyConfig: boolean,
-  hasConfig: boolean,
-  canAdmin: boolean | undefined,
-  alm: AlmKeys
-) {
-  if (hasTooManyConfig) {
-    return translateWithParameters(
-      'onboarding.create_project.too_many_alm_instances_X',
-      translate('alm', alm)
-    );
-  } else if (!hasConfig) {
+function getErrorMessage(hasConfig: boolean, canAdmin: boolean | undefined) {
+  if (!hasConfig) {
     return canAdmin
       ? translate('onboarding.create_project.alm_not_configured.admin')
       : translate('onboarding.create_project.alm_not_configured');
   }
-}
-
-function getMode(
-  isBitbucketOption: boolean,
-  hasBitbucketCloudConf: boolean,
-  mode: CreateProjectModes
-) {
-  return isBitbucketOption && hasBitbucketCloudConf ? CreateProjectModes.BitbucketCloud : mode;
+  return undefined;
 }
 
 function renderAlmOption(
   props: CreateProjectModeSelectionProps,
-  alm: AlmKeys.Azure | AlmKeys.BitbucketServer | AlmKeys.GitHub | AlmKeys.GitLab,
+  alm: AlmKeysUnion,
   mode: CreateProjectModes,
   last = false
 ) {
@@ -80,30 +62,26 @@ function renderAlmOption(
     appState: { canAdmin },
     loadingBindings,
   } = props;
-
-  const hasBitbucketCloudConf = almCounts[AlmKeys.BitbucketCloud] > 0;
-  const isBitbucketOption = alm === AlmKeys.BitbucketServer;
-
-  const count = isBitbucketOption
-    ? almCounts[AlmKeys.BitbucketServer] + almCounts[AlmKeys.BitbucketCloud]
-    : almCounts[alm];
+  const count = almCounts[alm];
   const hasConfig = count > 0;
-  const hasTooManyConfig = count > 1 && !ALLOWED_MULTIPLE_CONFIGS.includes(alm);
-  const disabled = loadingBindings || hasTooManyConfig || (!hasConfig && !canAdmin);
+  const disabled = loadingBindings || (!hasConfig && !canAdmin);
 
   const onClick = () => {
-    if (hasTooManyConfig || (!hasConfig && !canAdmin)) {
+    if (!hasConfig && !canAdmin) {
       return null;
     }
 
     if (!hasConfig && canAdmin) {
-      return props.onConfigMode(alm);
+      const configMode = alm === AlmKeys.BitbucketCloud ? AlmKeys.BitbucketServer : alm;
+      return props.onConfigMode(configMode);
     }
 
-    return props.onSelectMode(getMode(isBitbucketOption, hasBitbucketCloudConf, mode));
+    return props.onSelectMode(mode);
   };
 
-  const errorMessage = getErrorMessage(hasTooManyConfig, hasConfig, canAdmin, alm);
+  const errorMessage = getErrorMessage(hasConfig, canAdmin);
+
+  const svgFileName = alm === AlmKeys.BitbucketCloud ? AlmKeys.BitbucketServer : alm;
 
   return (
     <div className="display-flex-column">
@@ -119,7 +97,7 @@ function renderAlmOption(
         <img
           alt="" // Should be ignored by screen readers
           height={DEFAULT_ICON_SIZE}
-          src={`${getBaseUrl()}/images/alm/${alm}.svg`}
+          src={`${getBaseUrl()}/images/alm/${svgFileName}.svg`}
         />
         <div className="medium big-spacer-top abs-height-50 display-flex-center">
           {translate('onboarding.create_project.select_method', alm)}
@@ -164,6 +142,7 @@ export function CreateProjectModeSelection(props: CreateProjectModeSelectionProp
       <div className="big-spacer-top huge-spacer-bottom display-flex-center">
         {renderAlmOption(props, AlmKeys.Azure, CreateProjectModes.AzureDevOps)}
         {renderAlmOption(props, AlmKeys.BitbucketServer, CreateProjectModes.BitbucketServer)}
+        {renderAlmOption(props, AlmKeys.BitbucketCloud, CreateProjectModes.BitbucketCloud)}
         {renderAlmOption(props, AlmKeys.GitHub, CreateProjectModes.GitHub)}
         {renderAlmOption(props, AlmKeys.GitLab, CreateProjectModes.GitLab, true)}
       </div>
index aa6f2ff06b60001571704d062dd9f14a44db55da..34660ee0bb8dfc70585be790116139950ec47621 100644 (file)
@@ -179,7 +179,7 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp
         return (
           <BitbucketProjectCreate
             canAdmin={!!canAdmin}
-            bitbucketSettings={bitbucketSettings}
+            almInstances={bitbucketSettings}
             loadingBindings={loading}
             location={location}
             onProjectCreate={this.handleProjectCreate}
@@ -195,7 +195,7 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp
             location={location}
             onProjectCreate={this.handleProjectCreate}
             router={router}
-            settings={bitbucketCloudSettings}
+            almInstances={bitbucketCloudSettings}
           />
         );
       }
index 7d2263cc0b41274cb686f499374f34ac97cef42f..4aee42212ca05755aa8fd96ef5495185132aa650 100644 (file)
@@ -46,10 +46,10 @@ jest.mock('../../../../api/alm-integrations', () => {
 });
 
 it('Should render correctly', async () => {
-  const wrapper = shallowRender({ settings: [] });
+  const wrapper = shallowRender({ almInstances: [] });
   await waitAndUpdate(wrapper);
   expect(wrapper).toMatchSnapshot();
-  wrapper.setProps({ settings: [mockBitbucketCloudAlmSettingsInstance()] });
+  wrapper.setProps({ almInstances: [mockBitbucketCloudAlmSettingsInstance()] });
   await waitAndUpdate(wrapper);
   expect(wrapper).toMatchSnapshot('Setting changeds');
 });
@@ -174,7 +174,7 @@ function shallowRender(props?: Partial<BitbucketCloudProjectCreate['props']>) {
       location={mockLocation()}
       canAdmin={true}
       router={mockRouter()}
-      settings={[mockBitbucketCloudAlmSettingsInstance()]}
+      almInstances={[mockBitbucketCloudAlmSettingsInstance()]}
       {...props}
     />
   );
index 951aa97fbd163ba36152819a6326059ff77527f3..43c4f5c6b80541af4e0fa92b64ea8dff16a1f9fa 100644 (file)
@@ -26,7 +26,7 @@ import BitbucketCloudProjectCreateRenderer, {
 
 it('Should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot();
-  expect(shallowRender({ settings: undefined })).toMatchSnapshot('Wrong config');
+  expect(shallowRender({ selectedAlmInstance: undefined })).toMatchSnapshot('Wrong config');
   expect(shallowRender({ loading: true })).toMatchSnapshot('Loading...');
   expect(
     shallowRender({
@@ -48,7 +48,9 @@ function shallowRender(props?: Partial<BitbucketCloudProjectCreateRendererProps>
       resetPat={false}
       searching={false}
       searchQuery=""
-      settings={mockBitbucketCloudAlmSettingsInstance()}
+      selectedAlmInstance={mockBitbucketCloudAlmSettingsInstance()}
+      almInstances={[mockBitbucketCloudAlmSettingsInstance()]}
+      onSelectedAlmInstanceChange={jest.fn()}
       showPersonalAccessTokenForm={false}
       {...props}
     />
index c4d108190cad47bba2beb16cac84adcfefa5f3dd..91a6ea85605c2266653df478fa4d067292ea6931 100644 (file)
@@ -66,7 +66,7 @@ beforeEach(jest.clearAllMocks);
 
 it('should render correctly', async () => {
   expect(shallowRender()).toMatchSnapshot();
-  expect(shallowRender({ bitbucketSettings: [] })).toMatchSnapshot('No setting');
+  expect(shallowRender({ almInstances: [] })).toMatchSnapshot('No setting');
 
   const wrapper = shallowRender();
   (getBitbucketServerRepositories as jest.Mock).mockRejectedValueOnce({});
@@ -130,7 +130,7 @@ it('should correctly handle search', async () => {
 });
 
 it('should behave correctly when no setting', async () => {
-  const wrapper = shallowRender({ bitbucketSettings: [] });
+  const wrapper = shallowRender({ almInstances: [] });
   await wrapper.instance().handleSearch('');
   await wrapper.instance().handleImportRepository();
   await wrapper.instance().fetchBitbucketRepositories([mockBitbucketProject()]);
@@ -144,7 +144,7 @@ function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) {
   return shallow<BitbucketProjectCreate>(
     <BitbucketProjectCreate
       canAdmin={false}
-      bitbucketSettings={[mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer, key: 'foo' })]}
+      almInstances={[mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer, key: 'foo' })]}
       loadingBindings={false}
       location={mockLocation()}
       router={mockRouter()}
index d771028e1dff1232493a1c4bf1a28bd65fa00c2a..41dc80e94e2e74e0ddee00a4e4cc0a0631219f42 100644 (file)
@@ -37,10 +37,10 @@ it('should render correctly', () => {
   expect(shallowRender({ selectedRepository: mockBitbucketRepository() })).toMatchSnapshot(
     'selected repo'
   );
-  expect(shallowRender({ bitbucketSetting: undefined })).toMatchSnapshot(
+  expect(shallowRender({ selectedAlmInstance: undefined })).toMatchSnapshot(
     'invalid config, regular user'
   );
-  expect(shallowRender({ bitbucketSetting: undefined, canAdmin: true })).toMatchSnapshot(
+  expect(shallowRender({ selectedAlmInstance: undefined, canAdmin: true })).toMatchSnapshot(
     'invalid config, admin user'
   );
 });
@@ -48,7 +48,9 @@ it('should render correctly', () => {
 function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {}) {
   return shallow<BitbucketProjectCreateRendererProps>(
     <BitbucketProjectCreateRenderer
-      bitbucketSetting={mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer })}
+      selectedAlmInstance={mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer })}
+      almInstances={[mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer })]}
+      onSelectedAlmInstanceChange={jest.fn()}
       importing={false}
       loading={false}
       onImportRepository={jest.fn()}
index 0301387ae44db16393fe2e36bc6926cccea93ab0..e677e2a580dbbe1953c0b8d9a66484982177c9b3 100644 (file)
@@ -79,10 +79,14 @@ it('should correctly pass the selected mode up', () => {
   onSelectMode.mockClear();
 
   click(wrapper.find(almButton).at(2));
-  expect(onSelectMode).toHaveBeenCalledWith(CreateProjectModes.GitHub);
+  expect(onSelectMode).toHaveBeenCalledWith(CreateProjectModes.BitbucketCloud);
   onSelectMode.mockClear();
 
   click(wrapper.find(almButton).at(3));
+  expect(onSelectMode).toHaveBeenCalledWith(CreateProjectModes.GitHub);
+  onSelectMode.mockClear();
+
+  click(wrapper.find(almButton).at(4));
   expect(onSelectMode).toHaveBeenCalledWith(CreateProjectModes.GitLab);
   onSelectMode.mockClear();
 
@@ -91,7 +95,7 @@ it('should correctly pass the selected mode up', () => {
     { [AlmKeys.BitbucketCloud]: 1, [AlmKeys.BitbucketServer]: 0 }
   );
 
-  click(wrapper.find(almButton).at(1));
+  click(wrapper.find(almButton).at(2));
   expect(onSelectMode).toHaveBeenCalledWith(CreateProjectModes.BitbucketCloud);
   onSelectMode.mockClear();
 });
@@ -136,7 +140,7 @@ function shallowRender(
 ) {
   const almCounts = {
     [AlmKeys.Azure]: 1,
-    [AlmKeys.BitbucketCloud]: 0,
+    [AlmKeys.BitbucketCloud]: 1,
     [AlmKeys.BitbucketServer]: 1,
     [AlmKeys.GitHub]: 1,
     [AlmKeys.GitLab]: 1,
index 8861fde15411856abb5f5d155fd2cf2944856448..efaf694bc729163b5074ee99601505819abbda61 100644 (file)
@@ -2,6 +2,7 @@
 
 exports[`Should render correctly 1`] = `
 <BitbucketCloudProjectCreateRenderer
+  almInstances={Array []}
   canAdmin={true}
   isLastPage={true}
   loading={false}
@@ -10,6 +11,7 @@ exports[`Should render correctly 1`] = `
   onLoadMore={[Function]}
   onPersonalAccessTokenCreated={[Function]}
   onSearch={[Function]}
+  onSelectedAlmInstanceChange={[Function]}
   repositories={Array []}
   resetPat={false}
   searchQuery=""
@@ -20,6 +22,14 @@ exports[`Should render correctly 1`] = `
 
 exports[`Should render correctly: Setting changeds 1`] = `
 <BitbucketCloudProjectCreateRenderer
+  almInstances={
+    Array [
+      Object {
+        "alm": "bitbucketcloud",
+        "key": "key",
+      },
+    ]
+  }
   canAdmin={true}
   isLastPage={true}
   loading={false}
@@ -28,11 +38,12 @@ exports[`Should render correctly: Setting changeds 1`] = `
   onLoadMore={[Function]}
   onPersonalAccessTokenCreated={[Function]}
   onSearch={[Function]}
+  onSelectedAlmInstanceChange={[Function]}
   repositories={Array []}
   resetPat={false}
   searchQuery=""
   searching={false}
-  settings={
+  selectedAlmInstance={
     Object {
       "alm": "bitbucketcloud",
       "key": "key",
index eb413b12cc67fa9d65935903656037c6f8e780ed..ef927ced83ac7a3ff5379a10388b81aea091891d 100644 (file)
@@ -17,6 +17,23 @@ exports[`Should render correctly 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucketcloud",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+    selectedAlmInstance={
+      Object {
+        "alm": "bitbucketcloud",
+        "key": "key",
+      }
+    }
+  />
   <BitbucketCloudSearchForm
     isLastPage={true}
     loadingMore={false}
@@ -46,6 +63,23 @@ exports[`Should render correctly: Loading... 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucketcloud",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+    selectedAlmInstance={
+      Object {
+        "alm": "bitbucketcloud",
+        "key": "key",
+      }
+    }
+  />
   <i
     className="spinner"
   />
@@ -69,6 +103,23 @@ exports[`Should render correctly: Need App password 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucketcloud",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+    selectedAlmInstance={
+      Object {
+        "alm": "bitbucketcloud",
+        "key": "key",
+      }
+    }
+  />
   <PersonalAccessTokenForm
     almSetting={
       Object {
@@ -99,6 +150,17 @@ exports[`Should render correctly: Wrong config 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucketcloud",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+  />
   <WrongBindingCountAlert
     alm="bitbucketcloud"
     canAdmin={false}
index c06e0b818bf822ac03f97c8cf3db4ab32dd8b616..e168e70e45b22839bab64d349131d4745e3f1e7d 100644 (file)
@@ -2,11 +2,13 @@
 
 exports[`should render correctly 1`] = `
 <BitbucketProjectCreateRenderer
-  bitbucketSetting={
-    Object {
-      "alm": "bitbucket",
-      "key": "foo",
-    }
+  almInstances={
+    Array [
+      Object {
+        "alm": "bitbucket",
+        "key": "foo",
+      },
+    ]
   }
   canAdmin={false}
   importing={false}
@@ -15,19 +17,28 @@ exports[`should render correctly 1`] = `
   onPersonalAccessTokenCreated={[Function]}
   onSearch={[Function]}
   onSelectRepository={[Function]}
+  onSelectedAlmInstanceChange={[Function]}
   resetPat={false}
   searching={false}
+  selectedAlmInstance={
+    Object {
+      "alm": "bitbucket",
+      "key": "foo",
+    }
+  }
   showPersonalAccessTokenForm={true}
 />
 `;
 
 exports[`should render correctly: No repository 1`] = `
 <BitbucketProjectCreateRenderer
-  bitbucketSetting={
-    Object {
-      "alm": "bitbucket",
-      "key": "foo",
-    }
+  almInstances={
+    Array [
+      Object {
+        "alm": "bitbucket",
+        "key": "foo",
+      },
+    ]
   }
   canAdmin={false}
   importing={false}
@@ -36,6 +47,7 @@ exports[`should render correctly: No repository 1`] = `
   onPersonalAccessTokenCreated={[Function]}
   onSearch={[Function]}
   onSelectRepository={[Function]}
+  onSelectedAlmInstanceChange={[Function]}
   projects={
     Array [
       Object {
@@ -52,12 +64,19 @@ exports[`should render correctly: No repository 1`] = `
   }
   resetPat={false}
   searching={false}
+  selectedAlmInstance={
+    Object {
+      "alm": "bitbucket",
+      "key": "foo",
+    }
+  }
   showPersonalAccessTokenForm={false}
 />
 `;
 
 exports[`should render correctly: No setting 1`] = `
 <BitbucketProjectCreateRenderer
+  almInstances={Array []}
   canAdmin={false}
   importing={false}
   loading={false}
@@ -65,6 +84,7 @@ exports[`should render correctly: No setting 1`] = `
   onPersonalAccessTokenCreated={[Function]}
   onSearch={[Function]}
   onSelectRepository={[Function]}
+  onSelectedAlmInstanceChange={[Function]}
   resetPat={false}
   searching={false}
   showPersonalAccessTokenForm={true}
index c499c0acceaaf2c33874646b46cec098ff65a266..b9f83f3dd5c542daca15166b58626f4c7fd0feef 100644 (file)
@@ -34,6 +34,23 @@ exports[`should render correctly: default 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucket",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+    selectedAlmInstance={
+      Object {
+        "alm": "bitbucket",
+        "key": "key",
+      }
+    }
+  />
   <BitbucketImportRepositoryForm
     disableRepositories={false}
     onSearch={[MockFunction]}
@@ -101,6 +118,23 @@ exports[`should render correctly: importing 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucket",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+    selectedAlmInstance={
+      Object {
+        "alm": "bitbucket",
+        "key": "key",
+      }
+    }
+  />
   <BitbucketImportRepositoryForm
     disableRepositories={true}
     onSearch={[MockFunction]}
@@ -168,6 +202,17 @@ exports[`should render correctly: invalid config, admin user 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucket",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+  />
   <WrongBindingCountAlert
     alm="bitbucket"
     canAdmin={true}
@@ -209,6 +254,17 @@ exports[`should render correctly: invalid config, regular user 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucket",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+  />
   <WrongBindingCountAlert
     alm="bitbucket"
     canAdmin={false}
@@ -250,6 +306,23 @@ exports[`should render correctly: loading 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucket",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+    selectedAlmInstance={
+      Object {
+        "alm": "bitbucket",
+        "key": "key",
+      }
+    }
+  />
   <i
     className="spinner"
   />
@@ -274,6 +347,23 @@ exports[`should render correctly: pat form 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucket",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+    selectedAlmInstance={
+      Object {
+        "alm": "bitbucket",
+        "key": "key",
+      }
+    }
+  />
   <PersonalAccessTokenForm
     almSetting={
       Object {
@@ -321,6 +411,23 @@ exports[`should render correctly: selected repo 1`] = `
       </span>
     }
   />
+  <AlmSettingsInstanceDropdown
+    almInstances={
+      Array [
+        Object {
+          "alm": "bitbucket",
+          "key": "key",
+        },
+      ]
+    }
+    onChangeConfig={[MockFunction]}
+    selectedAlmInstance={
+      Object {
+        "alm": "bitbucket",
+        "key": "key",
+      }
+    }
+  />
   <BitbucketImportRepositoryForm
     disableRepositories={false}
     onSearch={[MockFunction]}
index 2d6c27f9ac77d1265f9002d1cc50d6e3c1e6e871..36a8a852f6b9f8f6337e5bb3170857ac1b7df229 100644 (file)
@@ -55,6 +55,27 @@ exports[`should render correctly: default 1`] = `
         </div>
       </button>
     </div>
+    <div
+      className="display-flex-column"
+    >
+      <button
+        className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right"
+        disabled={false}
+        onClick={[Function]}
+        type="button"
+      >
+        <img
+          alt=""
+          height={50}
+          src="/images/alm/bitbucket.svg"
+        />
+        <div
+          className="medium big-spacer-top abs-height-50 display-flex-center"
+        >
+          onboarding.create_project.select_method.bitbucketcloud
+        </div>
+      </button>
+    </div>
     <div
       className="display-flex-column"
     >
@@ -185,6 +206,27 @@ exports[`should render correctly: invalid configs, admin 1`] = `
         </p>
       </button>
     </div>
+    <div
+      className="display-flex-column"
+    >
+      <button
+        className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right"
+        disabled={false}
+        onClick={[Function]}
+        type="button"
+      >
+        <img
+          alt=""
+          height={50}
+          src="/images/alm/bitbucket.svg"
+        />
+        <div
+          className="medium big-spacer-top abs-height-50 display-flex-center"
+        >
+          onboarding.create_project.select_method.bitbucketcloud
+        </div>
+      </button>
+    </div>
     <div
       className="display-flex-column"
     >
@@ -315,6 +357,37 @@ exports[`should render correctly: invalid configs, admin 2`] = `
         </p>
       </button>
     </div>
+    <div
+      className="display-flex-column"
+    >
+      <button
+        className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right"
+        disabled={false}
+        onClick={[Function]}
+        type="button"
+      >
+        <img
+          alt=""
+          height={50}
+          src="/images/alm/bitbucket.svg"
+        />
+        <div
+          className="medium big-spacer-top abs-height-50 display-flex-center"
+        >
+          onboarding.create_project.select_method.bitbucketcloud
+        </div>
+        <p
+          className="text-muted small spacer-top"
+          style={
+            Object {
+              "lineHeight": 1.5,
+            }
+          }
+        >
+          onboarding.create_project.alm_not_configured.admin
+        </p>
+      </button>
+    </div>
     <div
       className="display-flex-column"
     >
@@ -445,6 +518,27 @@ exports[`should render correctly: invalid configs, not admin 1`] = `
         </p>
       </button>
     </div>
+    <div
+      className="display-flex-column"
+    >
+      <button
+        className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right"
+        disabled={false}
+        onClick={[Function]}
+        type="button"
+      >
+        <img
+          alt=""
+          height={50}
+          src="/images/alm/bitbucket.svg"
+        />
+        <div
+          className="medium big-spacer-top abs-height-50 display-flex-center"
+        >
+          onboarding.create_project.select_method.bitbucketcloud
+        </div>
+      </button>
+    </div>
     <div
       className="display-flex-column"
     >
@@ -577,6 +671,33 @@ exports[`should render correctly: loading instances 1`] = `
         </span>
       </button>
     </div>
+    <div
+      className="display-flex-column"
+    >
+      <button
+        className="button button-huge display-flex-column create-project-mode-type-alm disabled big-spacer-right"
+        disabled={true}
+        onClick={[Function]}
+        type="button"
+      >
+        <img
+          alt=""
+          height={50}
+          src="/images/alm/bitbucket.svg"
+        />
+        <div
+          className="medium big-spacer-top abs-height-50 display-flex-center"
+        >
+          onboarding.create_project.select_method.bitbucketcloud
+        </div>
+        <span>
+          onboarding.create_project.check_alm_supported
+          <i
+            className="little-spacer-left spinner"
+          />
+        </span>
+      </button>
+    </div>
     <div
       className="display-flex-column"
     >
@@ -734,6 +855,37 @@ exports[`should render correctly: no alm conf yet, admin 1`] = `
         </p>
       </button>
     </div>
+    <div
+      className="display-flex-column"
+    >
+      <button
+        className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right"
+        disabled={false}
+        onClick={[Function]}
+        type="button"
+      >
+        <img
+          alt=""
+          height={50}
+          src="/images/alm/bitbucket.svg"
+        />
+        <div
+          className="medium big-spacer-top abs-height-50 display-flex-center"
+        >
+          onboarding.create_project.select_method.bitbucketcloud
+        </div>
+        <p
+          className="text-muted small spacer-top"
+          style={
+            Object {
+              "lineHeight": 1.5,
+            }
+          }
+        >
+          onboarding.create_project.alm_not_configured.admin
+        </p>
+      </button>
+    </div>
     <div
       className="display-flex-column"
     >
index 623e4d2b0dc50c5f3ee4ac069640928d49f07b1a..6ec13628e53bf5de62a5ef3c30b5aa7b9e83c27d 100644 (file)
@@ -143,7 +143,7 @@ exports[`should render correctly for bitbucket mode 1`] = `
     id="create-project"
   >
     <BitbucketProjectCreate
-      bitbucketSettings={Array []}
+      almInstances={Array []}
       canAdmin={false}
       loadingBindings={true}
       location={
@@ -194,6 +194,7 @@ exports[`should render correctly for bitbucketcloud mode 1`] = `
     id="create-project"
   >
     <BitbucketCloudProjectCreate
+      almInstances={Array []}
       canAdmin={false}
       loadingBindings={true}
       location={
@@ -222,7 +223,6 @@ exports[`should render correctly for bitbucketcloud mode 1`] = `
           "setRouteLeaveHook": [MockFunction],
         }
       }
-      settings={Array []}
     />
   </div>
 </Fragment>
index 68567ef7eea7a1396ae2de76a8261778be9c602a..d47426c1831b2b1e4b773915acee00f5b7ff89cf 100644 (file)
@@ -1,5 +1,3 @@
-import { AlmKeys } from '../../../types/alm-settings';
-
 /*
  * SonarQube
  * Copyright (C) 2009-2022 SonarSource SA
@@ -22,5 +20,3 @@ import { AlmKeys } from '../../../types/alm-settings';
 export const PROJECT_NAME_MAX_LEN = 255;
 
 export const DEFAULT_BBS_PAGE_SIZE = 25;
-
-export const ALLOWED_MULTIPLE_CONFIGS = [AlmKeys.GitLab, AlmKeys.Azure, AlmKeys.GitHub];
index 97abfd16f81a6fd83bfc998e88b05f687aa3ebd3..b764b4dbd14a516a868924a8712c322e3428d499 100644 (file)
@@ -31,7 +31,6 @@ import { hasGlobalPermission } from '../../../helpers/users';
 import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
 import { Permissions } from '../../../types/permissions';
 import { LoggedInUser } from '../../../types/users';
-import { ALLOWED_MULTIPLE_CONFIGS } from '../../create/project/constants';
 import ProjectCreationMenuItem from './ProjectCreationMenuItem';
 
 interface Props {
@@ -80,18 +79,10 @@ export class ProjectCreationMenu extends React.PureComponent<Props, State> {
 
     const almSettings: AlmSettingsInstance[] = await getAlmSettings().catch(() => []);
 
-    // Import is only available if exactly one binding is configured
     const boundAlms = IMPORT_COMPATIBLE_ALMS.filter((key) => {
-      let currentAlmSettings: AlmSettingsInstance[];
-      if (key === AlmKeys.BitbucketServer || key === AlmKeys.BitbucketCloud) {
-        currentAlmSettings = almSettings.filter(
-          (s) => s.alm === AlmKeys.BitbucketCloud || s.alm === AlmKeys.BitbucketServer
-        );
-      } else {
-        currentAlmSettings = almSettings.filter((s) => s.alm === key);
-      }
+      const currentAlmSettings = almSettings.filter((s) => s.alm === key);
       return (
-        this.configLengthChecker(key, currentAlmSettings.length) &&
+        currentAlmSettings.length > 0 &&
         key === currentAlmSettings[0].alm &&
         this.almSettingIsValid(currentAlmSettings[0])
       );
@@ -104,10 +95,6 @@ export class ProjectCreationMenu extends React.PureComponent<Props, State> {
     }
   };
 
-  configLengthChecker = (key: AlmKeys, length: number) => {
-    return ALLOWED_MULTIPLE_CONFIGS.includes(key) ? length > 0 : length === 1;
-  };
-
   render() {
     const { className, currentUser } = this.props;
     const { boundAlms } = this.state;
index a73c1ea599960ab7b506ccfbe6714457a636f71b..87b621ecebeef77c06cfc6bfa691ebc3ae3cbfd2 100644 (file)
@@ -117,11 +117,22 @@ it('should filter alm bindings appropriately', async () => {
 
   wrapper = shallowRender();
   await waitAndUpdate(wrapper);
-  expect(wrapper.state().boundAlms).toEqual([AlmKeys.Azure, AlmKeys.GitHub, AlmKeys.GitLab]);
+  expect(wrapper.state().boundAlms).toEqual([
+    AlmKeys.Azure,
+    AlmKeys.BitbucketServer,
+    AlmKeys.BitbucketCloud,
+    AlmKeys.GitHub,
+    AlmKeys.GitLab,
+  ]);
 
   wrapper = shallowRender();
   await waitAndUpdate(wrapper);
-  expect(wrapper.state().boundAlms).toEqual([AlmKeys.Azure, AlmKeys.GitHub, AlmKeys.GitLab]);
+  expect(wrapper.state().boundAlms).toEqual([
+    AlmKeys.Azure,
+    AlmKeys.BitbucketServer,
+    AlmKeys.GitHub,
+    AlmKeys.GitLab,
+  ]);
 });
 
 function shallowRender(overrides: Partial<ProjectCreationMenu['props']> = {}) {
index ee27acc7694df10301d6a043e3bcf7137b6f3227..2d75e7a76d82146ee9790de692d02b5453f53c36 100644 (file)
@@ -38,13 +38,11 @@ import {
   AlmSettingsBindingStatusType,
 } from '../../../../types/alm-settings';
 import { EditionKey } from '../../../../types/editions';
-import { ALLOWED_MULTIPLE_CONFIGS } from '../../../create/project/constants';
 
 export interface AlmBindingDefinitionBoxProps {
   alm: AlmKeys;
   branchesEnabled: boolean;
   definition: AlmBindingDefinitionBase;
-  multipleDefinitions: boolean;
   onCheck: (definitionKey: string) => void;
   onDelete: (definitionKey: string) => void;
   onEdit: (definitionKey: string) => void;
@@ -108,23 +106,8 @@ function getPRDecorationFeatureStatus(
 function getImportFeatureStatus(
   alm: AlmKeys,
   definition: AlmBindingDefinitionBase,
-  multipleDefinitions: boolean,
   type: AlmSettingsBindingStatusType.Success | AlmSettingsBindingStatusType.Failure
 ) {
-  if (multipleDefinitions && !ALLOWED_MULTIPLE_CONFIGS.includes(alm)) {
-    return (
-      <div className="display-inline-flex-center">
-        <strong className="spacer-left">
-          {translate('settings.almintegration.feature.alm_repo_import.disabled')}
-        </strong>
-        <HelpTooltip
-          className="little-spacer-left"
-          overlay={translate('settings.almintegration.feature.alm_repo_import.disabled.multiple')}
-        />
-      </div>
-    );
-  }
-
   if (!definition.url && alm !== AlmKeys.BitbucketCloud) {
     return (
       <div className="display-inline-flex-center">
@@ -156,7 +139,7 @@ function getPrDecoFeatureDescription(alm: AlmKeys) {
 }
 
 export default function AlmBindingDefinitionBox(props: AlmBindingDefinitionBoxProps) {
-  const { alm, branchesEnabled, definition, multipleDefinitions, status = DEFAULT_STATUS } = props;
+  const { alm, branchesEnabled, definition, status = DEFAULT_STATUS } = props;
 
   return (
     <div className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition">
@@ -202,7 +185,7 @@ export default function AlmBindingDefinitionBox(props: AlmBindingDefinitionBoxPr
                       {translate('settings.almintegration.feature.alm_repo_import.title')}
                     </span>
                   </Tooltip>
-                  {getImportFeatureStatus(alm, definition, multipleDefinitions, status.type)}
+                  {getImportFeatureStatus(alm, definition, status.type)}
                 </div>
               )}
             </div>
index 6e1382cc881c667c1496aa3a47bd0ee31c32ff17..f36e4f931c7a07a4e0067179ae20bf80713d3285 100644 (file)
@@ -107,7 +107,6 @@ export default function AlmTabRenderer(props: AlmTabRendererProps) {
               branchesEnabled={branchesEnabled}
               definition={def}
               key={def.key}
-              multipleDefinitions={definitions.length > 1}
               onCheck={props.onCheck}
               onDelete={props.onDelete}
               onEdit={props.onEdit}
index 8a1f5e2754c6d84721317f1e3eb7add9e7c2e4e6..e32365f2f57a16de74457bfbdb7e1bb1666cf802 100644 (file)
@@ -31,7 +31,6 @@ import AlmBindingDefinitionBox, { AlmBindingDefinitionBoxProps } from '../AlmBin
 
 it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot('default');
-  expect(shallowRender({ multipleDefinitions: true })).toMatchSnapshot('multiple definitions');
   expect(
     shallowRender({
       status: mockAlmSettingsBindingStatus({
@@ -106,7 +105,6 @@ function shallowRender(props: Partial<AlmBindingDefinitionBoxProps> = {}) {
       alm={AlmKeys.GitHub}
       branchesEnabled={true}
       definition={mockGithubBindingDefinition()}
-      multipleDefinitions={false}
       onCheck={jest.fn()}
       onDelete={jest.fn()}
       onEdit={jest.fn()}
index 4afcf1ec1337ca3f5f58ceb3ddd95561c9648d06..afbd6543c065443fd1bbfa74780e163ceb11b392 100644 (file)
@@ -164,48 +164,6 @@ exports[`should render correctly: error 1`] = `
 </div>
 `;
 
-exports[`should render correctly: multiple definitions 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>
-    <span>
-      http://github.enterprise.com
-    </span>
-  </div>
-  <i
-    className="spinner spacer-right"
-  />
-  settings.almintegration.checking_configuration
-</div>
-`;
-
 exports[`should render correctly: success 1`] = `
 <div
   className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
index 20c590100f6a5c48cb93d15d230492de55d1459e..9adea1fe6477743bbb2c7d109ff920307532a473 100644 (file)
@@ -39,7 +39,6 @@ exports[`should render correctly for multi-ALM binding: editing a definition 1`]
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
@@ -88,7 +87,6 @@ exports[`should render correctly for multi-ALM binding: loaded 1`] = `
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
@@ -137,7 +135,6 @@ exports[`should render correctly for multi-ALM binding: loading ALM definitions
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
@@ -186,7 +183,6 @@ exports[`should render correctly for multi-ALM binding: loading project count 1`
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
@@ -235,7 +231,6 @@ exports[`should render correctly for single-ALM binding 1`] = `
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
@@ -284,7 +279,6 @@ exports[`should render correctly for single-ALM binding 2`] = `
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
@@ -333,7 +327,6 @@ exports[`should render correctly for single-ALM binding 3`] = `
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
@@ -426,7 +419,6 @@ exports[`should render correctly with validation: create a second 1`] = `
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
@@ -480,7 +472,6 @@ exports[`should render correctly with validation: default 1`] = `
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
@@ -570,7 +561,6 @@ exports[`should render correctly with validation: pass the correct key for bitbu
           }
         }
         key="key"
-        multipleDefinitions={false}
         onCheck={[MockFunction]}
         onDelete={[MockFunction]}
         onEdit={[MockFunction]}
index f6609284236eeb286f06908d5a51a71171568e23..8487e41e2f963caaf231212864fe6773e631b7e3 100644 (file)
@@ -25,6 +25,13 @@ export const enum AlmKeys {
   GitLab = 'gitlab',
 }
 
+export type AlmKeysUnion =
+  | AlmKeys.Azure
+  | AlmKeys.BitbucketServer
+  | AlmKeys.BitbucketCloud
+  | AlmKeys.GitHub
+  | AlmKeys.GitLab;
+
 export type AlmBindingDefinition =
   | AzureBindingDefinition
   | GithubBindingDefinition
index a0405c19920caec9c504b55a5bb9242328856a67..89d24f005ce68643897f5b7e5029864d52270e2c 100644 (file)
@@ -2122,8 +2122,8 @@ my_account.create_new.TRK=Add a project
 my_account.add_project=Add Project
 my_account.add_project.manual=Manually
 my_account.add_project.azure=Azure DevOps
-my_account.add_project.bitbucket=Bitbucket
-my_account.add_project.bitbucketcloud=Bitbucket
+my_account.add_project.bitbucket=Bitbucket Server
+my_account.add_project.bitbucketcloud=Bitbucket Cloud
 my_account.add_project.github=GitHub
 my_account.add_project.gitlab=GitLab
 my_account.reset_password=Update your password
@@ -3487,7 +3487,8 @@ onboarding.create_project.select_method.devops_platform=Do you want to benefit f
 onboarding.create_project.select_method.no_alm_yet.admin=First, you need to set up a DevOps platform configuration.
 onboarding.create_project.select_method.manual=Manually
 onboarding.create_project.select_method.azure=From Azure DevOps
-onboarding.create_project.select_method.bitbucket=From Bitbucket
+onboarding.create_project.select_method.bitbucket=From Bitbucket Server
+onboarding.create_project.select_method.bitbucketcloud=From Bitbucket Cloud
 onboarding.create_project.select_method.github=From GitHub
 onboarding.create_project.select_method.gitlab=From GitLab
 onboarding.create_project.alm_not_configured=Contact admin to set up global configuration
@@ -3516,7 +3517,7 @@ onboarding.create_project.search_projects_repositories=Search for projects and r
 onboarding.create_project.search_repositories=Search for a repository
 onboarding.create_project.select_repositories=Select repositories
 onboarding.create_project.select_all_repositories=Select all available repositories
-onboarding.create_project.from_bbs=Create a project from Bitbucket Server
+onboarding.create_project.from_bbs=Bitbucket Server project onboarding
 
 onboarding.create_application.key.description=If specified, this value is used as the key instead of generating it from the name of the Application. Only letters, digits, dashes and underscores can be used.
 
@@ -3532,8 +3533,8 @@ onboarding.create_project.pat_form.pat_required=Please enter a personal access t
 onboarding.create_project.pat_form.list_repositories=List repositories
 onboarding.create_project.select_method=How do you want to create your project?
 onboarding.create_project.too_many_alm_instances_X=This method requires exactly one {0} configuration.
-onboarding.create_project.wrong_binding_count=You must have exactly 1 {alm} instance configured in order to use this method, but none were found. Either create the project manually, or contact your system administrator.
-onboarding.create_project.wrong_binding_count.admin=You must have exactly 1 {alm} instance configured in order to use this method. You can configure instances under {url}.
+onboarding.create_project.wrong_binding_count=You must have at least 1 {alm} instance configured in order to use this method, but none were found. Either create the project manually, or contact your system administrator.
+onboarding.create_project.wrong_binding_count.admin=You must have at least 1 {alm} instance configured in order to use this method. You can configure instances under {url}.
 onboarding.create_project.azure.no_url.admin=Your Azure DevOps instance configuration is missing a URL. We cannot import projects in the current state. You can configure instances under {url}.
 onboarding.create_project.azure.no_url=Your Azure DevOps instance configuration is missing a URL. We cannot import projects in the current state. Please contact your system administrator.
 onboarding.create_project.enter_pat=Enter personal access token
@@ -3589,7 +3590,7 @@ onboarding.create_project.azure.no_projects=No projects could be fetched from Az
 onboarding.create_project.azure.search_results_for_project_X=Search results for "{0}"
 onboarding.create_project.azure.no_repositories=Could not fetch repositories for this project. Contact your system administrator, or {link}.
 onboarding.create_project.azure.no_results=No repositories match your search query.
-onboarding.create_project.bitbucketcloud.title=Which Bitbucket Cloud repository do you want to set up?
+onboarding.create_project.bitbucketcloud.title=Bitbucket Cloud project onboarding
 onboarding.create_project.bitbucketcloud.no_projects=No projects could be fetched from Bitbucket. Contact your system administrator, or {link}.
 onboarding.create_project.bitbucketcloud.link=See on Bitbucket
 onboarding.create_project.github.title=Github project onboarding