@@ -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> | |||
)} |
@@ -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); |
@@ -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} |
@@ -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} /> | |||
</> |
@@ -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} | |||
/> | |||
); | |||
@@ -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> | |||
); |
@@ -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({ |
@@ -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" | |||
> |
@@ -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} /> |
@@ -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> |
@@ -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<EmptyOverviewProps> = {}) { | |||
return shallow<EmptyOverviewProps>( | |||
<EmptyOverview | |||
branchLike={mockMainBranch()} | |||
branchLikes={[mockMainBranch()]} |
@@ -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" |
@@ -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> | |||
); | |||
} |
@@ -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', () => { |
@@ -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> | |||
`; |
@@ -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 |
@@ -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); | |||
}); |
@@ -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 |