]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14440 Better message for Branch Analysis tooltip
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Tue, 30 Mar 2021 07:12:35 +0000 (09:12 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 1 Apr 2021 20:03:45 +0000 (20:03 +0000)
18 files changed:
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
server/sonar-web/src/main/js/app/components/nav/component/Header.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/components/App.tsx
server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap
server/sonar-web/src/main/js/apps/tutorials/components/TutorialsApp.tsx
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TutorialsApp-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index a86a2e7cc9d9e360dfdd641dc2218d09d2de5818..caf68e4eb0eca432e3c4555f3d2a0893910fa5ba 100644 (file)
@@ -20,6 +20,7 @@
 import { differenceBy } from 'lodash';
 import * as React from 'react';
 import { connect } from 'react-redux';
+import { getProjectAlmBinding } from '../../api/alm-settings';
 import { getBranches, getPullRequests } from '../../api/branches';
 import { getAnalysisStatus, getTasksForComponent } from '../../api/ce';
 import { getComponentData } from '../../api/components';
@@ -33,6 +34,7 @@ import {
 } from '../../helpers/branch-like';
 import { getPortfolioUrl } from '../../helpers/urls';
 import { registerBranchStatus, requireAuthorization } from '../../store/rootActions';
+import { ProjectAlmBindingResponse } from '../../types/alm-settings';
 import { BranchLike } from '../../types/branch-like';
 import { isPortfolioLike } from '../../types/component';
 import { Task, TaskStatuses, TaskWarning } from '../../types/tasks';
@@ -56,6 +58,7 @@ interface State {
   currentTask?: Task;
   isPending: boolean;
   loading: boolean;
+  projectBinding?: ProjectAlmBindingResponse;
   tasksInProgress?: Task[];
   warnings: TaskWarning[];
 }
@@ -108,9 +111,10 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
 
     Promise.all([
       getComponentNavigation({ component: key, branch, pullRequest }),
-      getComponentData({ component: key, branch, pullRequest })
+      getComponentData({ component: key, branch, pullRequest }),
+      getProjectAlmBinding(key).catch(() => undefined)
     ])
-      .then(([nav, { component }]) => {
+      .then(([nav, { component }, projectBinding]) => {
         const componentWithQualifier = this.addQualifier({ ...nav, ...component });
 
         /*
@@ -125,6 +129,10 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
           this.props.router.replace(getPortfolioUrl(component.key));
         }
 
+        if (this.mounted) {
+          this.setState({ projectBinding });
+        }
+
         return componentWithQualifier;
       }, onError)
       .then(this.fetchBranches)
@@ -333,7 +341,14 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
       return <PageUnavailableDueToIndexation component={component} />;
     }
 
-    const { branchLike, branchLikes, currentTask, isPending, tasksInProgress } = this.state;
+    const {
+      branchLike,
+      branchLikes,
+      currentTask,
+      isPending,
+      projectBinding,
+      tasksInProgress
+    } = this.state;
     const isInProgress = tasksInProgress && tasksInProgress.length > 0;
 
     return (
@@ -349,6 +364,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
             isPending={isPending}
             onComponentChange={this.handleComponentChange}
             onWarningDismiss={this.handleWarningDismiss}
+            projectBinding={projectBinding}
             warnings={this.state.warnings}
           />
         )}
@@ -365,7 +381,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
               isInProgress,
               isPending,
               onBranchesChange: this.handleBranchesChange,
-              onComponentChange: this.handleComponentChange
+              onComponentChange: this.handleComponentChange,
+              projectBinding
             })}
           </ComponentContext.Provider>
         )}
index 6a0d13028d12a6f6d5a9b942c3e9e30ac0e6dcf0..570244785482e494293b33335fb75bbdc3238799 100644 (file)
@@ -20,6 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { getProjectAlmBinding } from '../../../api/alm-settings';
 import { getBranches, getPullRequests } from '../../../api/branches';
 import { getAnalysisStatus, getTasksForComponent } from '../../../api/ce';
 import { getComponentData } from '../../../api/components';
@@ -27,6 +28,7 @@ import { getComponentNavigation } from '../../../api/nav';
 import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like';
 import { mockTask } from '../../../helpers/mocks/tasks';
 import { mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks';
+import { AlmKeys } from '../../../types/alm-settings';
 import { ComponentQualifier } from '../../../types/component';
 import { TaskStatuses } from '../../../types/tasks';
 import { ComponentContainer } from '../ComponentContainer';
@@ -65,6 +67,10 @@ jest.mock('../../../api/nav', () => ({
   })
 }));
 
+jest.mock('../../../api/alm-settings', () => ({
+  getProjectAlmBinding: jest.fn().mockResolvedValue(undefined)
+}));
+
 // mock this, because some of its children are using redux store
 jest.mock('../nav/component/ComponentNav', () => ({
   default: () => null
@@ -88,6 +94,22 @@ it('changes component', () => {
   expect(wrapper.state().component).toEqual({ qualifier: 'TRK', visibility: 'private' });
 });
 
+it('loads the project binding, if any', async () => {
+  (getProjectAlmBinding as jest.Mock).mockResolvedValueOnce(undefined).mockResolvedValueOnce({
+    alm: AlmKeys.GitHub,
+    key: 'foo'
+  });
+
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(getProjectAlmBinding).toBeCalled();
+  expect(wrapper.state().projectBinding).toBeUndefined();
+
+  wrapper.setProps({ location: mockLocation({ query: { id: 'bar' } }) });
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state().projectBinding).toEqual({ alm: AlmKeys.GitHub, key: 'foo' });
+});
+
 it("doesn't load branches portfolio", async () => {
   const wrapper = shallowRender({ location: mockLocation({ query: { id: 'portfolioKey' } }) });
   await new Promise(setImmediate);
index 0410159d9a759a0199ad3573f74fef3f88f97be4..b0c0ab3c14de133bb0c7abfc8247fc05fd1dcb90 100644 (file)
@@ -20,6 +20,7 @@
 import * as classNames from 'classnames';
 import * as React from 'react';
 import ContextNavBar from 'sonar-ui-common/components/ui/ContextNavBar';
+import { ProjectAlmBindingResponse } from '../../../../types/alm-settings';
 import { BranchLike } from '../../../../types/branch-like';
 import { ComponentQualifier } from '../../../../types/component';
 import { Task, TaskStatuses, TaskWarning } from '../../../../types/tasks';
@@ -42,6 +43,7 @@ export interface ComponentNavProps {
   isPending?: boolean;
   onComponentChange: (changes: Partial<T.Component>) => void;
   onWarningDismiss: () => void;
+  projectBinding?: ProjectAlmBindingResponse;
   warnings: TaskWarning[];
 }
 
@@ -54,6 +56,7 @@ export default function ComponentNav(props: ComponentNavProps) {
     currentTaskOnSameBranch,
     isInProgress,
     isPending,
+    projectBinding,
     warnings
   } = props;
   const { contextNavHeightRaw, globalNavHeightRaw } = rawSizes;
@@ -100,6 +103,7 @@ export default function ComponentNav(props: ComponentNavProps) {
           branchLikes={branchLikes}
           component={component}
           currentBranchLike={currentBranchLike}
+          projectBinding={projectBinding}
         />
         <HeaderMeta
           branchLike={currentBranchLike}
index 929d9d61e959203dca561cfb72bb8a88c2a21cc8..dfbb27a8db7066bd47b76d1a4c8bd50580320394 100644 (file)
@@ -23,6 +23,7 @@ import { connect } from 'react-redux';
 import Favorite from '../../../../components/controls/Favorite';
 import { isLoggedIn } from '../../../../helpers/users';
 import { getCurrentUser, Store } from '../../../../store/rootReducer';
+import { ProjectAlmBindingResponse } from '../../../../types/alm-settings';
 import { BranchLike } from '../../../../types/branch-like';
 import BranchLikeNavigation from './branch-like/BranchLikeNavigation';
 import CurrentBranchLikeMergeInformation from './branch-like/CurrentBranchLikeMergeInformation';
@@ -33,10 +34,11 @@ export interface HeaderProps {
   component: T.Component;
   currentBranchLike: BranchLike | undefined;
   currentUser: T.CurrentUser;
+  projectBinding?: ProjectAlmBindingResponse;
 }
 
 export function Header(props: HeaderProps) {
-  const { branchLikes, component, currentBranchLike, currentUser } = props;
+  const { branchLikes, component, currentBranchLike, currentUser, projectBinding } = props;
 
   return (
     <>
@@ -57,6 +59,7 @@ export function Header(props: HeaderProps) {
               branchLikes={branchLikes}
               component={component}
               currentBranchLike={currentBranchLike}
+              projectBinding={projectBinding}
             />
             <CurrentBranchLikeMergeInformation currentBranchLike={currentBranchLike} />
           </>
index 72729ff47ce200402020badf098bba3730a628fc..4f4d13cdf987ba8b746d51e2c3ffd40ceca44a3d 100644 (file)
@@ -21,6 +21,7 @@ import * as classNames from 'classnames';
 import * as React from 'react';
 import Toggler from 'sonar-ui-common/components/controls/Toggler';
 import { withAppState } from '../../../../../components/hoc/withAppState';
+import { ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
 import { BranchLike } from '../../../../../types/branch-like';
 import './BranchLikeNavigation.css';
 import CurrentBranchLike from './CurrentBranchLike';
@@ -31,6 +32,7 @@ export interface BranchLikeNavigationProps {
   branchLikes: BranchLike[];
   component: T.Component;
   currentBranchLike: BranchLike;
+  projectBinding?: ProjectAlmBindingResponse;
 }
 
 export function BranchLikeNavigation(props: BranchLikeNavigationProps) {
@@ -39,7 +41,8 @@ export function BranchLikeNavigation(props: BranchLikeNavigationProps) {
     branchLikes,
     component,
     component: { configuration },
-    currentBranchLike
+    currentBranchLike,
+    projectBinding
   } = props;
 
   const [isMenuOpen, setIsMenuOpen] = React.useState(false);
@@ -54,6 +57,7 @@ export function BranchLikeNavigation(props: BranchLikeNavigationProps) {
       component={component}
       currentBranchLike={currentBranchLike}
       hasManyBranches={hasManyBranches}
+      projectBinding={projectBinding}
     />
   );
 
index 63a819444bc9fd1a6c59b61a18c86da819c85054..263974cc43bb7a6042792ce0b2def0543f9a0212 100644 (file)
@@ -22,11 +22,12 @@ import { Link } from 'react-router';
 import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
 import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon';
 import PlusCircleIcon from 'sonar-ui-common/components/icons/PlusCircleIcon';
-import { translate } from 'sonar-ui-common/helpers/l10n';
+import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
 import DocumentationTooltip from '../../../../../components/common/DocumentationTooltip';
 import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon';
 import { getBranchLikeDisplayName } from '../../../../../helpers/branch-like';
 import { getApplicationAdminUrl } from '../../../../../helpers/urls';
+import { AlmKeys, ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
 import { BranchLike } from '../../../../../types/branch-like';
 import { ComponentQualifier } from '../../../../../types/component';
 import { colors } from '../../../../theme';
@@ -36,6 +37,7 @@ export interface CurrentBranchLikeProps {
   component: T.Component;
   currentBranchLike: BranchLike;
   hasManyBranches: boolean;
+  projectBinding?: ProjectAlmBindingResponse;
 }
 
 export function CurrentBranchLike(props: CurrentBranchLikeProps) {
@@ -44,12 +46,14 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) {
     component,
     component: { configuration },
     currentBranchLike,
-    hasManyBranches
+    hasManyBranches,
+    projectBinding
   } = props;
 
   const displayName = getBranchLikeDisplayName(currentBranchLike);
   const isApplication = component.qualifier === ComponentQualifier.Application;
   const canAdminComponent = configuration && configuration.showSettings;
+  const isGitLab = projectBinding !== undefined && projectBinding.alm === AlmKeys.GitLab;
 
   const additionalIcon = () => {
     const plusIcon = <PlusCircleIcon fill={colors.blue} size={12} />;
@@ -79,7 +83,14 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) {
       if (!branchesEnabled) {
         return (
           <DocumentationTooltip
-            content={translate('branch_like_navigation.no_branch_support.content')}
+            content={
+              projectBinding !== undefined
+                ? translateWithParameters(
+                    `branch_like_navigation.no_branch_support.content_x.${isGitLab ? 'mr' : 'pr'}`,
+                    translate('alm', projectBinding.alm)
+                  )
+                : translate('branch_like_navigation.no_branch_support.content')
+            }
             data-test="branches-support-disabled"
             links={[
               {
@@ -87,7 +98,14 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) {
                 label: translate('learn_more')
               }
             ]}
-            title={translate('branch_like_navigation.no_branch_support.title')}>
+            title={
+              projectBinding !== undefined
+                ? translate(
+                    'branch_like_navigation.no_branch_support.title',
+                    isGitLab ? 'mr' : 'pr'
+                  )
+                : translate('branch_like_navigation.no_branch_support.title')
+            }>
             {plusIcon}
           </DocumentationTooltip>
         );
index d288b69d0267803e702da430a42adea1ce6a51e8..7da6e02c498978ec38e6915313cef12007014835 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import {
+  mockProjectGithubBindingResponse,
+  mockProjectGitLabBindingResponse
+} from '../../../../../../helpers/mocks/alm-settings';
 import { mockMainBranch } from '../../../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../../../helpers/testMocks';
 import { ComponentQualifier } from '../../../../../../types/component';
 import { CurrentBranchLike, CurrentBranchLikeProps } from '../CurrentBranchLike';
 
-describe('CurrentBranchLikeRenderer should render correctly for application when', () => {
-  it('there is only one branch and the user can admin the application', () => {
+describe('applications', () => {
+  it('should render correctly when there is only one branch and the user can admin the application', () => {
     const wrapper = shallowRender({
       component: mockComponent({
         configuration: { showSettings: true },
@@ -36,7 +40,7 @@ describe('CurrentBranchLikeRenderer should render correctly for application when
     expect(wrapper).toMatchSnapshot();
   });
 
-  it("there is only one branch and the user CAN'T admin the application", () => {
+  it("should render correctly when there is only one branch and the user CAN'T admin the application", () => {
     const wrapper = shallowRender({
       component: mockComponent({
         configuration: { showSettings: false },
@@ -47,7 +51,7 @@ describe('CurrentBranchLikeRenderer should render correctly for application when
     expect(wrapper).toMatchSnapshot();
   });
 
-  it('there are many branchlikes', () => {
+  it('should render correctly when there are many branchlikes', () => {
     const wrapper = shallowRender({
       branchesEnabled: true,
       component: mockComponent({
@@ -59,18 +63,37 @@ describe('CurrentBranchLikeRenderer should render correctly for application when
   });
 });
 
-describe('CurrentBranchLikeRenderer should render correctly for project when', () => {
-  it('branches support is disabled', () => {
-    const wrapper = shallowRender({
-      branchesEnabled: false,
-      component: mockComponent({
-        qualifier: ComponentQualifier.Project
+describe('projects', () => {
+  it('should render correctly when branches support is disabled', () => {
+    expect(
+      shallowRender({
+        branchesEnabled: false,
+        component: mockComponent({
+          qualifier: ComponentQualifier.Project
+        })
       })
-    });
-    expect(wrapper).toMatchSnapshot();
+    ).toMatchSnapshot('default');
+    expect(
+      shallowRender({
+        branchesEnabled: false,
+        component: mockComponent({
+          qualifier: ComponentQualifier.Project
+        }),
+        projectBinding: mockProjectGithubBindingResponse()
+      })
+    ).toMatchSnapshot('alm with prs');
+    expect(
+      shallowRender({
+        branchesEnabled: false,
+        component: mockComponent({
+          qualifier: ComponentQualifier.Project
+        }),
+        projectBinding: mockProjectGitLabBindingResponse()
+      })
+    ).toMatchSnapshot('alm with mrs');
   });
 
-  it('there is only one branchlike', () => {
+  it('should render correctly when there is only one branchlike', () => {
     const wrapper = shallowRender({
       branchesEnabled: true,
       component: mockComponent({
@@ -81,7 +104,7 @@ describe('CurrentBranchLikeRenderer should render correctly for project when', (
     expect(wrapper).toMatchSnapshot();
   });
 
-  it('there are many branchlikes', () => {
+  it('should render correctly when there are many branchlikes', () => {
     const wrapper = shallowRender({
       branchesEnabled: true,
       component: mockComponent({
index db23d06a772a1db03257d911adaca493a8b74967..231a3776bc6fff1f0a0a28d1632a84469cdbcad9 100644 (file)
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`CurrentBranchLikeRenderer should render correctly for application when there are many branchlikes 1`] = `
+exports[`applications should render correctly when there are many branchlikes 1`] = `
 <span
   className="display-flex-center flex-shrink text-ellipsis"
 >
@@ -24,7 +24,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for application when
 </span>
 `;
 
-exports[`CurrentBranchLikeRenderer should render correctly for application when there is only one branch and the user CAN'T admin the application 1`] = `
+exports[`applications should render correctly when there is only one branch and the user CAN'T admin the application 1`] = `
 <span
   className="display-flex-center flex-shrink text-ellipsis"
 >
@@ -47,7 +47,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for application when
 </span>
 `;
 
-exports[`CurrentBranchLikeRenderer should render correctly for application when there is only one branch and the user can admin the application 1`] = `
+exports[`applications should render correctly when there is only one branch and the user can admin the application 1`] = `
 <span
   className="display-flex-center flex-shrink text-ellipsis"
 >
@@ -101,7 +101,89 @@ exports[`CurrentBranchLikeRenderer should render correctly for application when
 </span>
 `;
 
-exports[`CurrentBranchLikeRenderer should render correctly for project when branches support is disabled 1`] = `
+exports[`projects should render correctly when branches support is disabled: alm with mrs 1`] = `
+<span
+  className="display-flex-center flex-shrink text-ellipsis"
+>
+  <BranchLikeIcon
+    branchLike={
+      Object {
+        "analysisDate": "2018-01-01",
+        "excludedFromPurge": true,
+        "isMain": true,
+        "name": "master",
+      }
+    }
+  />
+  <span
+    className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
+    title="master"
+  >
+    master
+  </span>
+  <DocumentationTooltip
+    content="branch_like_navigation.no_branch_support.content_x.mr.alm.gitlab"
+    data-test="branches-support-disabled"
+    links={
+      Array [
+        Object {
+          "href": "https://redirect.sonarsource.com/editions/developer.html",
+          "label": "learn_more",
+        },
+      ]
+    }
+    title="branch_like_navigation.no_branch_support.title.mr"
+  >
+    <PlusCircleIcon
+      fill="#4b9fd5"
+      size={12}
+    />
+  </DocumentationTooltip>
+</span>
+`;
+
+exports[`projects should render correctly when branches support is disabled: alm with prs 1`] = `
+<span
+  className="display-flex-center flex-shrink text-ellipsis"
+>
+  <BranchLikeIcon
+    branchLike={
+      Object {
+        "analysisDate": "2018-01-01",
+        "excludedFromPurge": true,
+        "isMain": true,
+        "name": "master",
+      }
+    }
+  />
+  <span
+    className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
+    title="master"
+  >
+    master
+  </span>
+  <DocumentationTooltip
+    content="branch_like_navigation.no_branch_support.content_x.pr.alm.github"
+    data-test="branches-support-disabled"
+    links={
+      Array [
+        Object {
+          "href": "https://redirect.sonarsource.com/editions/developer.html",
+          "label": "learn_more",
+        },
+      ]
+    }
+    title="branch_like_navigation.no_branch_support.title.pr"
+  >
+    <PlusCircleIcon
+      fill="#4b9fd5"
+      size={12}
+    />
+  </DocumentationTooltip>
+</span>
+`;
+
+exports[`projects should render correctly when branches support is disabled: default 1`] = `
 <span
   className="display-flex-center flex-shrink text-ellipsis"
 >
@@ -142,7 +224,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for project when bran
 </span>
 `;
 
-exports[`CurrentBranchLikeRenderer should render correctly for project when there are many branchlikes 1`] = `
+exports[`projects should render correctly when there are many branchlikes 1`] = `
 <span
   className="display-flex-center flex-shrink text-ellipsis"
 >
@@ -166,7 +248,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for project when ther
 </span>
 `;
 
-exports[`CurrentBranchLikeRenderer should render correctly for project when there is only one branchlike 1`] = `
+exports[`projects should render correctly when there is only one branchlike 1`] = `
 <span
   className="display-flex-center flex-shrink text-ellipsis"
 >
index 1f10658c01b7a09a2a6c9288321c81ae1d9ac5e7..9f1da8a7d942a7bc1cf4bdb815d3843656284d01 100644 (file)
@@ -22,6 +22,7 @@ import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import { Router, withRouter } from '../../../components/hoc/withRouter';
 import { isPullRequest } from '../../../helpers/branch-like';
+import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
 import { BranchLike } from '../../../types/branch-like';
 import { isPortfolioLike } from '../../../types/component';
 import BranchOverview from '../branches/BranchOverview';
@@ -35,6 +36,7 @@ interface Props {
   component: T.Component;
   isInProgress?: boolean;
   isPending?: boolean;
+  projectBinding?: ProjectAlmBindingResponse;
   router: Pick<Router, 'replace'>;
 }
 
@@ -44,7 +46,7 @@ export class App extends React.PureComponent<Props> {
   };
 
   render() {
-    const { branchLike, branchLikes, component } = this.props;
+    const { branchLike, branchLikes, component, projectBinding } = this.props;
 
     if (this.isPortfolio()) {
       return null;
@@ -65,6 +67,7 @@ export class App extends React.PureComponent<Props> {
             branchLikes={branchLikes}
             component={component}
             hasAnalyses={this.props.isPending || this.props.isInProgress}
+            projectBinding={projectBinding}
           />
         ) : (
           <BranchOverview branch={branchLike} component={component} />
index 56598f7c26a3b1651197f15f9b806611fb4f0670..60514ea61011db5062209a5e434a86495cf5da0c 100644 (file)
@@ -26,19 +26,21 @@ import TutorialSelection from '../../../components/tutorials/TutorialSelection';
 import { getBranchLikeDisplayName, isBranch, isMainBranch } from '../../../helpers/branch-like';
 import { isLoggedIn } from '../../../helpers/users';
 import { getCurrentUser, Store } from '../../../store/rootReducer';
+import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
 import { BranchLike } from '../../../types/branch-like';
 import { ComponentQualifier } from '../../../types/component';
 
-interface Props {
+export interface EmptyOverviewProps {
   branchLike?: BranchLike;
   branchLikes: BranchLike[];
   component: T.Component;
   currentUser: T.CurrentUser;
   hasAnalyses?: boolean;
+  projectBinding?: ProjectAlmBindingResponse;
 }
 
-export function EmptyOverview(props: Props) {
-  const { branchLike, branchLikes, component, currentUser, hasAnalyses } = props;
+export function EmptyOverview(props: EmptyOverviewProps) {
+  const { branchLike, branchLikes, component, currentUser, hasAnalyses, projectBinding } = props;
 
   if (component.qualifier === ComponentQualifier.Application) {
     return (
@@ -87,7 +89,13 @@ export function EmptyOverview(props: Props) {
       {isLoggedIn(currentUser) ? (
         <>
           {showWarning && <Alert variant="warning">{warning}</Alert>}
-          {showTutorial && <TutorialSelection component={component} currentUser={currentUser} />}
+          {showTutorial && (
+            <TutorialSelection
+              component={component}
+              currentUser={currentUser}
+              projectBinding={projectBinding}
+            />
+          )}
         </>
       ) : (
         <Alert variant="warning">{warning}</Alert>
index e5ad238e5daedc169b2cea4253468d2674f5638d..932418ec3e8728fde172d63b571be6a45ceb486c 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { mockProjectGithubBindingResponse } from '../../../../helpers/mocks/alm-settings';
 import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like';
 import { mockComponent, mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
 import { ComponentQualifier } from '../../../../types/component';
-import { EmptyOverview } from '../EmptyOverview';
+import { EmptyOverview, EmptyOverviewProps } from '../EmptyOverview';
 
 it('renders correctly', () => {
   expect(shallowRender()).toMatchSnapshot();
   expect(shallowRender({ hasAnalyses: true })).toMatchSnapshot();
   expect(shallowRender({ currentUser: mockCurrentUser() })).toMatchSnapshot();
+  expect(shallowRender({ projectBinding: mockProjectGithubBindingResponse() })).toMatchSnapshot();
 });
 
 it('should render another message when there are branches', () => {
@@ -49,8 +51,8 @@ it('should not render the tutorial for applications', () => {
   ).toMatchSnapshot();
 });
 
-function shallowRender(props = {}) {
-  return shallow(
+function shallowRender(props: Partial<EmptyOverviewProps> = {}) {
+  return shallow<EmptyOverviewProps>(
     <EmptyOverview
       branchLike={mockMainBranch()}
       branchLikes={[mockMainBranch()]}
index abbcb357af1b4aceda3a78ed880f47d3c8bb84bc..9e3e14188d3de1572c10c1682cb24d1ccbdd7a7d 100644 (file)
@@ -67,6 +67,55 @@ exports[`renders correctly 3`] = `
 </div>
 `;
 
+exports[`renders correctly 4`] = `
+<div
+  className="page page-limited"
+>
+  <withRouter(TutorialSelection)
+    component={
+      Object {
+        "breadcrumbs": Array [],
+        "key": "my-project",
+        "name": "MyProject",
+        "qualifier": "TRK",
+        "qualityGate": Object {
+          "isDefault": true,
+          "key": "30",
+          "name": "Sonar way",
+        },
+        "qualityProfiles": Array [
+          Object {
+            "deleted": false,
+            "key": "my-qp",
+            "language": "ts",
+            "name": "Sonar way",
+          },
+        ],
+        "tags": Array [],
+        "version": "0.0.1",
+      }
+    }
+    currentUser={
+      Object {
+        "groups": Array [],
+        "isLoggedIn": true,
+        "login": "luke",
+        "name": "Skywalker",
+        "scmAccounts": Array [],
+      }
+    }
+    projectBinding={
+      Object {
+        "alm": "github",
+        "key": "foo",
+        "monorepo": true,
+        "repository": "PROJECT_KEY",
+      }
+    }
+  />
+</div>
+`;
+
 exports[`should not render the tutorial for applications 1`] = `
 <div
   className="page page-limited"
index 7720191b84aadade90afdac9e67a45075e9a217c..27dc42381ed2c53afc0a0425f608d3b828be86ba 100644 (file)
@@ -22,14 +22,16 @@ import handleRequiredAuthentication from 'sonar-ui-common/helpers/handleRequired
 import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
 import TutorialSelection from '../../../components/tutorials/TutorialSelection';
 import { isLoggedIn } from '../../../helpers/users';
+import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
 
 export interface TutorialsAppProps {
   component: T.Component;
   currentUser: T.CurrentUser;
+  projectBinding?: ProjectAlmBindingResponse;
 }
 
 export function TutorialsApp(props: TutorialsAppProps) {
-  const { component, currentUser } = props;
+  const { component, currentUser, projectBinding } = props;
 
   if (!isLoggedIn(currentUser)) {
     handleRequiredAuthentication();
@@ -38,7 +40,11 @@ export function TutorialsApp(props: TutorialsAppProps) {
 
   return (
     <div className="page page-limited">
-      <TutorialSelection component={component} currentUser={currentUser} />
+      <TutorialSelection
+        component={component}
+        currentUser={currentUser}
+        projectBinding={projectBinding}
+      />
     </div>
   );
 }
index 443e0efc00f1954333608339004d97fe09436cc4..bab74dbd72b60711d9123c6d664b7ecb05960b1c 100644 (file)
@@ -20,6 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import handleRequiredAuthentication from 'sonar-ui-common/helpers/handleRequiredAuthentication';
+import { mockProjectAzureBindingResponse } from '../../../../helpers/mocks/alm-settings';
 import { mockComponent, mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
 import { TutorialsApp, TutorialsAppProps } from '../TutorialsApp';
 
@@ -27,6 +28,7 @@ jest.mock('sonar-ui-common/helpers/handleRequiredAuthentication', () => ({ defau
 
 it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot();
+  expect(shallowRender({ projectBinding: mockProjectAzureBindingResponse() })).toMatchSnapshot();
 });
 
 it('should redirect if user is not logged in', () => {
index 7a8438fd0efb823e4331a377dc0eaec45cbabcc1..faea76c1c68cd262d67234ceeef42dd138ef60f4 100644 (file)
@@ -39,3 +39,53 @@ exports[`should render correctly 1`] = `
   />
 </div>
 `;
+
+exports[`should render correctly 2`] = `
+<div
+  className="page page-limited"
+>
+  <withRouter(TutorialSelection)
+    component={
+      Object {
+        "breadcrumbs": Array [],
+        "key": "my-project",
+        "name": "MyProject",
+        "qualifier": "TRK",
+        "qualityGate": Object {
+          "isDefault": true,
+          "key": "30",
+          "name": "Sonar way",
+        },
+        "qualityProfiles": Array [
+          Object {
+            "deleted": false,
+            "key": "my-qp",
+            "language": "ts",
+            "name": "Sonar way",
+          },
+        ],
+        "tags": Array [],
+      }
+    }
+    currentUser={
+      Object {
+        "groups": Array [],
+        "isLoggedIn": true,
+        "login": "luke",
+        "name": "Skywalker",
+        "scmAccounts": Array [],
+      }
+    }
+    projectBinding={
+      Object {
+        "alm": "azure",
+        "key": "foo",
+        "monorepo": false,
+        "repository": "REPOSITORY_NAME",
+        "slug": "PROJECT_NAME",
+        "url": "https://ado.my_company.com/mycollection",
+      }
+    }
+  />
+</div>
+`;
index ddf797e83c22f182912a6aec034a68b8801a89d0..28d0bc67435be31826210a98609cf79e9e57f509 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import { WithRouterProps } from 'react-router';
 import { getHostUrl } from 'sonar-ui-common/helpers/urls';
-import { getAlmDefinitionsNoCatch, getProjectAlmBinding } from '../../api/alm-settings';
+import { getAlmDefinitionsNoCatch } from '../../api/alm-settings';
 import { getValues } from '../../api/settings';
 import { AlmBindingDefinition, ProjectAlmBindingResponse } from '../../types/alm-settings';
 import { SettingsKey } from '../../types/settings';
@@ -31,6 +31,7 @@ import { TutorialModes } from './types';
 interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
   component: T.Component;
   currentUser: T.LoggedInUser;
+  projectBinding?: ProjectAlmBindingResponse;
 }
 
 interface State {
@@ -38,7 +39,6 @@ interface State {
   baseUrl: string;
   forceManual: boolean;
   loading: boolean;
-  projectBinding?: ProjectAlmBindingResponse;
 }
 
 export class TutorialSelection extends React.PureComponent<Props, State> {
@@ -64,23 +64,19 @@ export class TutorialSelection extends React.PureComponent<Props, State> {
   }
 
   fetchAlmBindings = async () => {
-    const { component } = this.props;
+    const { projectBinding } = this.props;
 
-    const [almDefinitions, projectBinding] = await Promise.all([
-      getAlmDefinitionsNoCatch().catch(() => undefined),
-      getProjectAlmBinding(component.key).catch(() => undefined)
-    ]);
-
-    if (this.mounted) {
-      if (projectBinding === undefined) {
-        this.setState({ forceManual: true });
-      } else {
+    if (projectBinding === undefined) {
+      this.setState({ forceManual: true });
+    } else {
+      const almDefinitions = await getAlmDefinitionsNoCatch().catch(() => undefined);
+      if (this.mounted) {
         let almBinding;
         if (almDefinitions !== undefined) {
           const specificDefinitions = almDefinitions[projectBinding.alm] as AlmBindingDefinition[];
           almBinding = specificDefinitions.find(d => d.key === projectBinding.key);
         }
-        this.setState({ almBinding, forceManual: false, projectBinding });
+        this.setState({ almBinding, forceManual: false });
       }
     }
   };
@@ -106,8 +102,8 @@ export class TutorialSelection extends React.PureComponent<Props, State> {
   };
 
   render() {
-    const { component, currentUser, location } = this.props;
-    const { almBinding, baseUrl, forceManual, loading, projectBinding } = this.state;
+    const { component, currentUser, location, projectBinding } = this.props;
+    const { almBinding, baseUrl, forceManual, loading } = this.state;
 
     const selectedTutorial: TutorialModes | undefined = forceManual
       ? TutorialModes.Manual
index 41bcdf0f729be28191b1d1973e9f33a67a6850aa..c2cc786bb0fd08272ea34c0b58cb740b72dd4f6c 100644 (file)
@@ -21,9 +21,12 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
 import { getHostUrl } from 'sonar-ui-common/helpers/urls';
-import { getAlmDefinitionsNoCatch, getProjectAlmBinding } from '../../../api/alm-settings';
+import { getAlmDefinitionsNoCatch } from '../../../api/alm-settings';
 import { getValues } from '../../../api/settings';
-import { mockBitbucketBindingDefinition } from '../../../helpers/mocks/alm-settings';
+import {
+  mockBitbucketBindingDefinition,
+  mockProjectBitbucketBindingResponse
+} from '../../../helpers/mocks/alm-settings';
 import {
   mockComponent,
   mockLocation,
@@ -40,7 +43,6 @@ jest.mock('sonar-ui-common/helpers/urls', () => ({
 }));
 
 jest.mock('../../../api/alm-settings', () => ({
-  getProjectAlmBinding: jest.fn().mockRejectedValue(null),
   getAlmDefinitionsNoCatch: jest.fn().mockRejectedValue(null)
 }));
 
@@ -61,8 +63,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.BitbucketServer });
-  const wrapper = shallowRender();
+  const wrapper = shallowRender({ projectBinding: mockProjectBitbucketBindingResponse() });
   await waitAndUpdate(wrapper);
   expect(wrapper.state().forceManual).toBe(false);
 });
@@ -70,11 +71,10 @@ 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.BitbucketServer, key });
   (getAlmDefinitionsNoCatch as jest.Mock).mockResolvedValueOnce({
     [AlmKeys.BitbucketServer]: [almBinding]
   });
-  const wrapper = shallowRender();
+  const wrapper = shallowRender({ projectBinding: mockProjectBitbucketBindingResponse({ key }) });
   await waitAndUpdate(wrapper);
   expect(wrapper.state().almBinding).toBe(almBinding);
 });
index 8fc86bed269158a6e9b8c582f85d5a6aa1920701..b7010c61c67c276fd0e43d02149206bcfcfc3046 100644 (file)
@@ -3756,8 +3756,12 @@ branch_like_navigation.pull_requests=Pull Requests
 branch_like_navigation.orphan_pull_requests=Orphan Pull Requests
 branch_like_navigation.orphan_pull_requests.tooltip=When the base of a Pull Request is deleted, this Pull Request becomes orphan.
 branch_like_navigation.for_merge_into_x_from_y=for merge into {target} from {branch}
-branch_like_navigation.no_branch_support.title=Get the most out of SonarQube with branches analysis
-branch_like_navigation.no_branch_support.content=Analyze each branch of your project separately with the Developer Edition.
+branch_like_navigation.no_branch_support.title=Get the most out of SonarQube with branch and PR/MR analysis
+branch_like_navigation.no_branch_support.title.pr=Get the most out of SonarQube with branch and PR analysis
+branch_like_navigation.no_branch_support.title.mr=Get the most out of SonarQube with branch and MR analysis
+branch_like_navigation.no_branch_support.content=With Developer Edition you can analyze every pull/merge request. You can also watch the quality of your release branches by analyzing each one individually in SonarQube.
+branch_like_navigation.no_branch_support.content_x.pr=With Developer Edition you can analyze every pull request and get the results in {0}. You can also watch the quality of your release branches by analyzing each one individually in SonarQube.
+branch_like_navigation.no_branch_support.content_x.mr=With Developer Edition you can analyze every merge request and get the results in {0}. You can also watch the quality of your release branches by analyzing each one individually in SonarQube.
 branch_like_navigation.only_one_branch.title=Learn how to analyze branches in SonarQube
 branch_like_navigation.only_one_branch.content=Quickly setup branch analysis and get separate insights for each of your branches and Pull Requests.
 branch_like_navigation.only_one_branch.documentation=Branches documentation