From 407f46439ced942e6c3244d50ab88e9486ab2301 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Tue, 30 Mar 2021 09:12:35 +0200 Subject: [PATCH] SONAR-14440 Better message for Branch Analysis tooltip --- .../js/app/components/ComponentContainer.tsx | 25 ++++- .../__tests__/ComponentContainer-test.tsx | 22 +++++ .../components/nav/component/ComponentNav.tsx | 4 + .../app/components/nav/component/Header.tsx | 5 +- .../branch-like/BranchLikeNavigation.tsx | 6 +- .../branch-like/CurrentBranchLike.tsx | 26 ++++- .../__tests__/CurrentBranchLike-test.tsx | 51 +++++++--- .../CurrentBranchLike-test.tsx.snap | 94 +++++++++++++++++-- .../main/js/apps/overview/components/App.tsx | 5 +- .../overview/components/EmptyOverview.tsx | 16 +++- .../__tests__/EmptyOverview-test.tsx | 8 +- .../__snapshots__/EmptyOverview-test.tsx.snap | 49 ++++++++++ .../tutorials/components/TutorialsApp.tsx | 10 +- .../__tests__/TutorialsApp-test.tsx | 2 + .../__snapshots__/TutorialsApp-test.tsx.snap | 50 ++++++++++ .../tutorials/TutorialSelection.tsx | 26 +++-- .../__tests__/TutorialSelection-test.tsx | 14 +-- .../resources/org/sonar/l10n/core.properties | 8 +- 18 files changed, 357 insertions(+), 64 deletions(-) diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index a86a2e7cc9d..caf68e4eb0e 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -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 { 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 { 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 { return ; } - 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 { isPending={isPending} onComponentChange={this.handleComponentChange} onWarningDismiss={this.handleWarningDismiss} + projectBinding={projectBinding} warnings={this.state.warnings} /> )} @@ -365,7 +381,8 @@ export class ComponentContainer extends React.PureComponent { isInProgress, isPending, onBranchesChange: this.handleBranchesChange, - onComponentChange: this.handleComponentChange + onComponentChange: this.handleComponentChange, + projectBinding })} )} diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx index 6a0d13028d1..57024478548 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx @@ -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); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx index 0410159d9a7..b0c0ab3c14d 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx @@ -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) => 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} /> @@ -57,6 +59,7 @@ export function Header(props: HeaderProps) { branchLikes={branchLikes} component={component} currentBranchLike={currentBranchLike} + projectBinding={projectBinding} /> diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx index 72729ff47ce..4f4d13cdf98 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx @@ -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} /> ); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx index 63a819444bc..263974cc43b 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx @@ -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 = ; @@ -79,7 +83,14 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) { if (!branchesEnabled) { return ( + title={ + projectBinding !== undefined + ? translate( + 'branch_like_navigation.no_branch_support.title', + isGitLab ? 'mr' : 'pr' + ) + : translate('branch_like_navigation.no_branch_support.title') + }> {plusIcon} ); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx index d288b69d026..7da6e02c498 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx @@ -19,13 +19,17 @@ */ 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({ diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap index db23d06a772..231a3776bc6 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap @@ -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`] = ` @@ -24,7 +24,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for application when `; -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`] = ` @@ -47,7 +47,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for application when `; -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`] = ` @@ -101,7 +101,89 @@ exports[`CurrentBranchLikeRenderer should render correctly for application when `; -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`] = ` + + + + master + + + + + +`; + +exports[`projects should render correctly when branches support is disabled: alm with prs 1`] = ` + + + + master + + + + + +`; + +exports[`projects should render correctly when branches support is disabled: default 1`] = ` @@ -142,7 +224,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for project when bran `; -exports[`CurrentBranchLikeRenderer should render correctly for project when there are many branchlikes 1`] = ` +exports[`projects should render correctly when there are many branchlikes 1`] = ` @@ -166,7 +248,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for project when ther `; -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`] = ` diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.tsx b/server/sonar-web/src/main/js/apps/overview/components/App.tsx index 1f10658c01b..9f1da8a7d94 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/App.tsx @@ -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; } @@ -44,7 +46,7 @@ export class App extends React.PureComponent { }; 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 { branchLikes={branchLikes} component={component} hasAnalyses={this.props.isPending || this.props.isInProgress} + projectBinding={projectBinding} /> ) : ( diff --git a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx b/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx index 56598f7c26a..60514ea6101 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx @@ -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 && {warning}} - {showTutorial && } + {showTutorial && ( + + )} ) : ( {warning} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx index e5ad238e5da..932418ec3e8 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx @@ -19,15 +19,17 @@ */ 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 = {}) { + return shallow( `; +exports[`renders correctly 4`] = ` +
+ +
+`; + exports[`should not render the tutorial for applications 1`] = `
- +
); } diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx index 443e0efc00f..bab74dbd72b 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx @@ -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', () => { diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TutorialsApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TutorialsApp-test.tsx.snap index 7a8438fd0ef..faea76c1c68 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TutorialsApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TutorialsApp-test.tsx.snap @@ -39,3 +39,53 @@ exports[`should render correctly 1`] = ` /> `; + +exports[`should render correctly 2`] = ` +
+ +
+`; diff --git a/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx b/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx index ddf797e83c2..28d0bc67435 100644 --- a/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx @@ -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 { 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 { @@ -64,23 +64,19 @@ export class TutorialSelection extends React.PureComponent { } 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 { }; 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 diff --git a/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx b/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx index 41bcdf0f729..c2cc786bb0f 100644 --- a/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx @@ -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); }); diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 8fc86bed269..b7010c61c67 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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 -- 2.39.5