@@ -67,7 +67,7 @@ import { formatMeasure } from 'sonar-ui-common/helpers/measures'; | |||
import NotFound from '../../../app/components/NotFound'; | |||
import Favorite from '../../../components/controls/Favorite'; | |||
import HomePageSelect from '../../../components/controls/HomePageSelect'; | |||
import BranchIcon from '../../../components/icons-components/BranchIcon'; | |||
import BranchLikeIcon from '../../../components/icons/BranchLikeIcon'; | |||
import DateFormatter from '../../../components/intl/DateFormatter'; | |||
import DateFromNow from '../../../components/intl/DateFromNow'; | |||
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; | |||
@@ -125,7 +125,7 @@ const exposeLibraries = () => { | |||
AlertErrorIcon, | |||
AlertSuccessIcon, | |||
AlertWarnIcon, | |||
BranchIcon, | |||
BranchIcon: BranchLikeIcon, | |||
Button, | |||
Checkbox, | |||
CheckIcon, |
@@ -0,0 +1,72 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { last } from 'lodash'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | |||
import { isMainBranch } from '../../../../helpers/branches'; | |||
import { getProjectUrl } from '../../../../helpers/urls'; | |||
interface Props { | |||
component: T.Component; | |||
currentBranchLike: T.BranchLike | undefined; | |||
} | |||
export function ComponentBreadcrumb(props: Props) { | |||
const { | |||
component: { breadcrumbs }, | |||
currentBranchLike | |||
} = props; | |||
const lastBreadcrumbElement = last(breadcrumbs); | |||
const isNoMainBranch = currentBranchLike && !isMainBranch(currentBranchLike); | |||
return ( | |||
<div className="big flex-shrink display-flex-center"> | |||
{breadcrumbs.map((breadcrumbElement, i) => { | |||
const isFirst = i === 0; | |||
const isNotLast = i < breadcrumbs.length - 1; | |||
return ( | |||
<span className="flex-shrink display-flex-center" key={breadcrumbElement.key}> | |||
{isFirst && lastBreadcrumbElement && ( | |||
<QualifierIcon className="spacer-right" qualifier={lastBreadcrumbElement.qualifier} /> | |||
)} | |||
{isNoMainBranch || isNotLast ? ( | |||
<Link | |||
className="link-no-underline text-ellipsis" | |||
title={breadcrumbElement.name} | |||
to={getProjectUrl(breadcrumbElement.key)}> | |||
{breadcrumbElement.name} | |||
</Link> | |||
) : ( | |||
<span className="text-ellipsis" title={breadcrumbElement.name}> | |||
{breadcrumbElement.name} | |||
</span> | |||
)} | |||
{isNotLast && <span className="slash-separator" />} | |||
</span> | |||
); | |||
})} | |||
</div> | |||
); | |||
} | |||
export default React.memo(ComponentBreadcrumb); |
@@ -17,21 +17,6 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
.navbar-context-branches { | |||
display: inline-flex; | |||
justify-content: center; | |||
align-items: center; | |||
flex-shrink: 1 !important; | |||
min-width: 0; | |||
line-height: calc(2 * var(--gridSize)); | |||
margin-left: calc(2 * var(--gridSize)); | |||
font-size: var(--baseFontSize); | |||
} | |||
.navbar-context-branches .popup { | |||
min-width: 430px; | |||
max-width: 650px; | |||
} | |||
.navbar-context-meta .alert { | |||
margin-bottom: 0; | |||
@@ -40,23 +25,3 @@ | |||
.navbar-context-meta .alert-content { | |||
padding: 6px 8px; | |||
} | |||
.navbar-context-meta-branch-menu-title { | |||
padding-left: calc(3 * var(--gridSize)); | |||
} | |||
.navbar-context-meta-branch-menu-item { | |||
display: flex !important; | |||
justify-content: space-between; | |||
align-items: center; | |||
} | |||
.navbar-context-meta-branch-menu-item-name { | |||
flex: 0 1 550px; /* Workaround for SONAR-10971 */ | |||
min-width: 0; | |||
} | |||
.navbar-context-meta-branch-menu-item-actions { | |||
height: 12px; | |||
margin-left: 32px; | |||
} |
@@ -91,8 +91,6 @@ export default class ComponentNav extends React.PureComponent<Props> { | |||
branchLikes={this.props.branchLikes} | |||
component={component} | |||
currentBranchLike={currentBranchLike} | |||
// to close dropdown on any location change | |||
location={this.props.location} | |||
/> | |||
<ComponentNavMeta | |||
branchLike={currentBranchLike} |
@@ -1,231 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { Link } from 'react-router'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import Toggler from 'sonar-ui-common/components/controls/Toggler'; | |||
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 DocTooltip from '../../../../components/docs/DocTooltip'; | |||
import { withAppState } from '../../../../components/hoc/withAppState'; | |||
import BranchIcon from '../../../../components/icons-components/BranchIcon'; | |||
import { | |||
getBranchLikeDisplayName, | |||
isPullRequest, | |||
isSameBranchLike, | |||
isShortLivingBranch | |||
} from '../../../../helpers/branches'; | |||
import { isSonarCloud } from '../../../../helpers/system'; | |||
import { getPortfolioAdminUrl } from '../../../../helpers/urls'; | |||
import { colors } from '../../../theme'; | |||
import ComponentNavBranchesMenu from './ComponentNavBranchesMenu'; | |||
interface Props { | |||
appState: Pick<T.AppState, 'branchesEnabled'>; | |||
branchLikes: T.BranchLike[]; | |||
component: T.Component; | |||
currentBranchLike: T.BranchLike; | |||
location?: any; | |||
} | |||
interface State { | |||
dropdownOpen: boolean; | |||
} | |||
export class ComponentNavBranch extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { dropdownOpen: false }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillReceiveProps(nextProps: Props) { | |||
if ( | |||
nextProps.component !== this.props.component || | |||
!isSameBranchLike(nextProps.currentBranchLike, this.props.currentBranchLike) || | |||
nextProps.location !== this.props.location | |||
) { | |||
this.setState({ dropdownOpen: false }); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
handleClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
event.stopPropagation(); | |||
event.currentTarget.blur(); | |||
this.setState(state => ({ dropdownOpen: !state.dropdownOpen })); | |||
}; | |||
closeDropdown = () => { | |||
if (this.mounted) { | |||
this.setState({ dropdownOpen: false }); | |||
} | |||
}; | |||
renderMergeBranch = () => { | |||
const { currentBranchLike } = this.props; | |||
if (isShortLivingBranch(currentBranchLike)) { | |||
return currentBranchLike.isOrphan ? ( | |||
<span className="note big-spacer-left text-ellipsis flex-shrink"> | |||
<span className="text-middle">{translate('branches.orphan_branch')}</span> | |||
<HelpTooltip | |||
className="spacer-left" | |||
overlay={translate('branches.orphan_branches.tooltip')} | |||
/> | |||
</span> | |||
) : ( | |||
<span className="note big-spacer-left"> | |||
{translate('from')} <strong>{currentBranchLike.mergeBranch}</strong> | |||
</span> | |||
); | |||
} else if (isPullRequest(currentBranchLike)) { | |||
return ( | |||
<span className="note big-spacer-left text-ellipsis flex-shrink"> | |||
<FormattedMessage | |||
defaultMessage={translate('branches.pull_request.for_merge_into_x_from_y')} | |||
id="branches.pull_request.for_merge_into_x_from_y" | |||
values={{ | |||
target: <strong>{currentBranchLike.target}</strong>, | |||
branch: <strong>{currentBranchLike.branch}</strong> | |||
}} | |||
/> | |||
</span> | |||
); | |||
} else { | |||
return null; | |||
} | |||
}; | |||
renderOverlay = () => { | |||
return ( | |||
<> | |||
<p>{translate('application.branches.help')}</p> | |||
<hr className="spacer-top spacer-bottom" /> | |||
<Link | |||
className="spacer-left link-no-underline" | |||
to={getPortfolioAdminUrl(this.props.component.breadcrumbs[0].key, 'APP')}> | |||
{translate('application.branches.link')} | |||
</Link> | |||
</> | |||
); | |||
}; | |||
render() { | |||
const { branchLikes, currentBranchLike } = this.props; | |||
const { configuration, breadcrumbs } = this.props.component; | |||
if (isSonarCloud() && !this.props.appState.branchesEnabled) { | |||
return null; | |||
} | |||
const displayName = getBranchLikeDisplayName(currentBranchLike); | |||
const isApp = breadcrumbs && breadcrumbs[0] && breadcrumbs[0].qualifier === 'APP'; | |||
if (isApp && branchLikes.length < 2) { | |||
return ( | |||
<div className="navbar-context-branches"> | |||
<BranchIcon | |||
branchLike={currentBranchLike} | |||
className="little-spacer-right" | |||
fill={colors.gray80} | |||
/> | |||
<span className="note">{displayName}</span> | |||
{configuration && configuration.showSettings && ( | |||
<HelpTooltip className="spacer-left" overlay={this.renderOverlay()}> | |||
<PlusCircleIcon className="text-middle" fill={colors.blue} size={12} /> | |||
</HelpTooltip> | |||
)} | |||
</div> | |||
); | |||
} else { | |||
if (!this.props.appState.branchesEnabled) { | |||
return ( | |||
<div className="navbar-context-branches"> | |||
<BranchIcon | |||
branchLike={currentBranchLike} | |||
className="little-spacer-right" | |||
fill={colors.gray80} | |||
/> | |||
<span className="note">{displayName}</span> | |||
<DocTooltip | |||
className="spacer-left" | |||
doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/branches/no-branch-support.md')}> | |||
<PlusCircleIcon fill={colors.gray71} size={12} /> | |||
</DocTooltip> | |||
</div> | |||
); | |||
} | |||
if (branchLikes.length < 2) { | |||
return ( | |||
<div className="navbar-context-branches"> | |||
<BranchIcon branchLike={currentBranchLike} className="little-spacer-right" /> | |||
<span className="note">{displayName}</span> | |||
<DocTooltip | |||
className="spacer-left" | |||
doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/branches/single-branch.md')}> | |||
<PlusCircleIcon fill={colors.blue} size={12} /> | |||
</DocTooltip> | |||
</div> | |||
); | |||
} | |||
} | |||
return ( | |||
<div className="navbar-context-branches"> | |||
<div className="dropdown"> | |||
<Toggler | |||
onRequestClose={this.closeDropdown} | |||
open={this.state.dropdownOpen} | |||
overlay={ | |||
<ComponentNavBranchesMenu | |||
branchLikes={this.props.branchLikes} | |||
canAdmin={configuration && configuration.showSettings} | |||
component={this.props.component} | |||
currentBranchLike={this.props.currentBranchLike} | |||
onClose={this.closeDropdown} | |||
/> | |||
}> | |||
<a | |||
className="link-base-color link-no-underline nowrap" | |||
href="#" | |||
onClick={this.handleClick}> | |||
<BranchIcon branchLike={currentBranchLike} className="little-spacer-right" /> | |||
<span className="text-limited text-top" title={displayName}> | |||
{displayName} | |||
</span> | |||
<DropdownIcon className="little-spacer-left" /> | |||
</a> | |||
</Toggler> | |||
</div> | |||
{this.renderMergeBranch()} | |||
</div> | |||
); | |||
} | |||
} | |||
export default withAppState(ComponentNavBranch); |
@@ -1,263 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import SearchBox from 'sonar-ui-common/components/controls/SearchBox'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; | |||
import { Router, withRouter } from '../../../../components/hoc/withRouter'; | |||
import { | |||
getBranchLikeKey, | |||
isBranch, | |||
isLongLivingBranch, | |||
isPullRequest, | |||
isSameBranchLike, | |||
isShortLivingBranch, | |||
sortBranchesAsTree | |||
} from '../../../../helpers/branches'; | |||
import { getBranchLikeUrl } from '../../../../helpers/urls'; | |||
import ComponentNavBranchesMenuItem from './ComponentNavBranchesMenuItem'; | |||
interface Props { | |||
branchLikes: T.BranchLike[]; | |||
canAdmin?: boolean; | |||
component: T.Component; | |||
currentBranchLike: T.BranchLike; | |||
onClose: () => void; | |||
router: Pick<Router, 'push'>; | |||
} | |||
interface State { | |||
query: string; | |||
selected: T.BranchLike | undefined; | |||
} | |||
export class ComponentNavBranchesMenu extends React.PureComponent<Props, State> { | |||
listNode?: HTMLUListElement | null; | |||
selectedBranchNode?: HTMLLIElement | null; | |||
state: State = { query: '', selected: undefined }; | |||
componentDidMount() { | |||
this.scrollToSelectedBranch(false); | |||
} | |||
componentDidUpdate() { | |||
this.scrollToSelectedBranch(true); | |||
} | |||
scrollToSelectedBranch(smooth: boolean) { | |||
if (this.listNode && this.selectedBranchNode) { | |||
scrollToElement(this.selectedBranchNode, { | |||
parent: this.listNode, | |||
smooth | |||
}); | |||
} | |||
} | |||
getFilteredBranchLikes = () => { | |||
const query = this.state.query.toLowerCase(); | |||
return sortBranchesAsTree(this.props.branchLikes).filter(branchLike => { | |||
const matchBranchName = isBranch(branchLike) && branchLike.name.toLowerCase().includes(query); | |||
const matchPullRequestTitleOrId = | |||
isPullRequest(branchLike) && | |||
(branchLike.title.toLowerCase().includes(query) || | |||
branchLike.key.toLowerCase().includes(query)); | |||
return matchBranchName || matchPullRequestTitleOrId; | |||
}); | |||
}; | |||
handleSearchChange = (query: string) => this.setState({ query, selected: undefined }); | |||
handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { | |||
switch (event.keyCode) { | |||
case 13: | |||
event.preventDefault(); | |||
this.openSelected(); | |||
return; | |||
case 38: | |||
event.preventDefault(); | |||
this.selectPrevious(); | |||
return; | |||
case 40: | |||
event.preventDefault(); | |||
this.selectNext(); | |||
// keep this return to prevent fall-through in case more cases will be adder later | |||
// eslint-disable-next-line no-useless-return | |||
return; | |||
} | |||
}; | |||
openSelected = () => { | |||
const selected = this.getSelected(); | |||
if (selected) { | |||
this.props.router.push(this.getProjectBranchUrl(selected)); | |||
} | |||
}; | |||
selectPrevious = () => { | |||
const selected = this.getSelected(); | |||
const branchLikes = this.getFilteredBranchLikes(); | |||
const index = branchLikes.findIndex(b => isSameBranchLike(b, selected)); | |||
if (index > 0) { | |||
this.setState({ selected: branchLikes[index - 1] }); | |||
} | |||
}; | |||
selectNext = () => { | |||
const selected = this.getSelected(); | |||
const branches = this.getFilteredBranchLikes(); | |||
const index = branches.findIndex(b => isSameBranchLike(b, selected)); | |||
if (index >= 0 && index < branches.length - 1) { | |||
this.setState({ selected: branches[index + 1] }); | |||
} | |||
}; | |||
handleSelect = (branchLike: T.BranchLike) => { | |||
this.setState({ selected: branchLike }); | |||
}; | |||
getSelected = () => { | |||
if (this.state.selected) { | |||
return this.state.selected; | |||
} | |||
const branchLikes = this.getFilteredBranchLikes(); | |||
if (branchLikes.find(b => isSameBranchLike(b, this.props.currentBranchLike))) { | |||
return this.props.currentBranchLike; | |||
} | |||
if (branchLikes.length > 0) { | |||
return branchLikes[0]; | |||
} | |||
return undefined; | |||
}; | |||
getProjectBranchUrl = (branchLike: T.BranchLike) => | |||
getBranchLikeUrl(this.props.component.key, branchLike); | |||
isOrphan = (branchLike: T.BranchLike) => { | |||
return (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && branchLike.isOrphan; | |||
}; | |||
renderSearch = () => ( | |||
<div className="menu-search"> | |||
<SearchBox | |||
autoFocus={true} | |||
onChange={this.handleSearchChange} | |||
onKeyDown={this.handleKeyDown} | |||
placeholder={translate('branches.search_for_branches')} | |||
value={this.state.query} | |||
/> | |||
</div> | |||
); | |||
renderBranchesList = () => { | |||
const branchLikes = this.getFilteredBranchLikes(); | |||
const selected = this.getSelected(); | |||
if (branchLikes.length === 0) { | |||
return <div className="menu-message note">{translate('no_results')}</div>; | |||
} | |||
const items = branchLikes.map((branchLike, index) => { | |||
const isOrphan = this.isOrphan(branchLike); | |||
const previous = index > 0 ? branchLikes[index - 1] : undefined; | |||
const isPreviousOrphan = previous !== undefined && this.isOrphan(previous); | |||
const showDivider = isLongLivingBranch(branchLike) || (isOrphan && !isPreviousOrphan); | |||
const showOrphanHeader = isOrphan && !isPreviousOrphan; | |||
const showPullRequestHeader = | |||
!showOrphanHeader && isPullRequest(branchLike) && !isPullRequest(previous); | |||
const showShortLivingBranchHeader = | |||
!showOrphanHeader && isShortLivingBranch(branchLike) && !isShortLivingBranch(previous); | |||
const isSelected = isSameBranchLike(branchLike, selected); | |||
return ( | |||
<React.Fragment key={getBranchLikeKey(branchLike)}> | |||
{showDivider && <li className="divider" />} | |||
{showOrphanHeader && ( | |||
<li className="menu-header"> | |||
<div className="display-inline-block text-middle"> | |||
{translate('branches.orphan_branches')} | |||
</div> | |||
<HelpTooltip | |||
className="spacer-left" | |||
overlay={translate('branches.orphan_branches.tooltip')} | |||
/> | |||
</li> | |||
)} | |||
{showPullRequestHeader && ( | |||
<li className="menu-header navbar-context-meta-branch-menu-title"> | |||
{translate('branches.pull_requests')} | |||
</li> | |||
)} | |||
{showShortLivingBranchHeader && ( | |||
<li className="menu-header navbar-context-meta-branch-menu-title"> | |||
{translate('branches.short_lived_branches')} | |||
</li> | |||
)} | |||
<ComponentNavBranchesMenuItem | |||
branchLike={branchLike} | |||
component={this.props.component} | |||
innerRef={node => | |||
(this.selectedBranchNode = isSelected ? node : this.selectedBranchNode) | |||
} | |||
key={getBranchLikeKey(branchLike)} | |||
onSelect={this.handleSelect} | |||
selected={isSelected} | |||
/> | |||
</React.Fragment> | |||
); | |||
}); | |||
return ( | |||
<ul className="menu menu-vertically-limited" ref={node => (this.listNode = node)}> | |||
{items} | |||
</ul> | |||
); | |||
}; | |||
render() { | |||
const { component } = this.props; | |||
const showManageLink = | |||
component.qualifier === 'TRK' && | |||
component.configuration && | |||
component.configuration.showSettings; | |||
return ( | |||
<DropdownOverlay noPadding={true}> | |||
{this.renderSearch()} | |||
{this.renderBranchesList()} | |||
{showManageLink && ( | |||
<div className="dropdown-bottom-hint text-right"> | |||
<Link | |||
className="text-muted" | |||
to={{ pathname: '/project/branches', query: { id: component.key } }}> | |||
{translate('branches.manage')} | |||
</Link> | |||
</div> | |||
)} | |||
</DropdownOverlay> | |||
); | |||
} | |||
} | |||
export default withRouter(ComponentNavBranchesMenu); |
@@ -1,77 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import BranchStatus from '../../../../components/common/BranchStatus'; | |||
import BranchIcon from '../../../../components/icons-components/BranchIcon'; | |||
import { | |||
getBranchLikeDisplayName, | |||
getBranchLikeKey, | |||
isMainBranch, | |||
isPullRequest, | |||
isShortLivingBranch | |||
} from '../../../../helpers/branches'; | |||
import { getBranchLikeUrl } from '../../../../helpers/urls'; | |||
export interface Props { | |||
branchLike: T.BranchLike; | |||
component: T.Component; | |||
onSelect: (branchLike: T.BranchLike) => void; | |||
selected: boolean; | |||
innerRef?: (node: HTMLLIElement) => void; | |||
} | |||
export default function ComponentNavBranchesMenuItem({ branchLike, ...props }: Props) { | |||
const handleMouseEnter = () => { | |||
props.onSelect(branchLike); | |||
}; | |||
const displayName = getBranchLikeDisplayName(branchLike); | |||
const shouldBeIndented = | |||
(isShortLivingBranch(branchLike) && !branchLike.isOrphan) || isPullRequest(branchLike); | |||
return ( | |||
<li key={getBranchLikeKey(branchLike)} onMouseEnter={handleMouseEnter} ref={props.innerRef}> | |||
<Link | |||
className={classNames('navbar-context-meta-branch-menu-item', { | |||
active: props.selected | |||
})} | |||
to={getBranchLikeUrl(props.component.key, branchLike)}> | |||
<div | |||
className="navbar-context-meta-branch-menu-item-name text-ellipsis" | |||
title={displayName}> | |||
<BranchIcon | |||
branchLike={branchLike} | |||
className={classNames('little-spacer-right', { 'big-spacer-left': shouldBeIndented })} | |||
/> | |||
{displayName} | |||
{isMainBranch(branchLike) && ( | |||
<div className="badge spacer-left">{translate('branches.main_branch')}</div> | |||
)} | |||
</div> | |||
<div className="big-spacer-left note"> | |||
<BranchStatus branchLike={branchLike} component={props.component.key} /> | |||
</div> | |||
</Link> | |||
</li> | |||
); | |||
} |
@@ -18,111 +18,38 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { Link } from 'react-router'; | |||
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | |||
import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; | |||
import OrganizationAvatar from '../../../../components/common/OrganizationAvatar'; | |||
import OrganizationHelmet from '../../../../components/common/OrganizationHelmet'; | |||
import OrganizationLink from '../../../../components/ui/OrganizationLink'; | |||
import { sanitizeAlmId } from '../../../../helpers/almIntegrations'; | |||
import { isMainBranch } from '../../../../helpers/branches'; | |||
import { isSonarCloud } from '../../../../helpers/system'; | |||
import { getProjectUrl } from '../../../../helpers/urls'; | |||
import { getOrganizationByKey, Store } from '../../../../store/rootReducer'; | |||
import ComponentNavBranch from './ComponentNavBranch'; | |||
import Helmet from 'react-helmet'; | |||
import BranchLikeNavigation from './branch-like/BranchLikeNavigation'; | |||
import CurrentBranchLikeMergeInformation from './branch-like/CurrentBranchLikeMergeInformation'; | |||
import { ComponentBreadcrumb } from './ComponentBreadcrumb'; | |||
interface StateProps { | |||
organization?: T.Organization; | |||
} | |||
interface OwnProps { | |||
export interface ComponentNavHeaderProps { | |||
branchLikes: T.BranchLike[]; | |||
component: T.Component; | |||
currentBranchLike: T.BranchLike | undefined; | |||
location?: any; | |||
} | |||
type Props = StateProps & OwnProps; | |||
export function ComponentNavHeader(props: Props) { | |||
const { component, organization } = props; | |||
export function ComponentNavHeader(props: ComponentNavHeaderProps) { | |||
const { branchLikes, component, currentBranchLike } = props; | |||
return ( | |||
<header className="navbar-context-header"> | |||
<OrganizationHelmet | |||
organization={organization && isSonarCloud() ? organization : undefined} | |||
title={component.name} | |||
/> | |||
{organization && isSonarCloud() && ( | |||
<> | |||
<OrganizationAvatar organization={organization} /> | |||
<OrganizationLink | |||
className="navbar-context-header-breadcrumb-link link-base-color link-no-underline spacer-left" | |||
organization={organization}> | |||
{organization.name} | |||
</OrganizationLink> | |||
<span className="slash-separator" /> | |||
</> | |||
)} | |||
{renderBreadcrumbs( | |||
component.breadcrumbs, | |||
props.currentBranchLike !== undefined && !isMainBranch(props.currentBranchLike) | |||
)} | |||
{isSonarCloud() && component.alm && ( | |||
<a | |||
className="link-no-underline" | |||
href={component.alm.url} | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
<img | |||
alt={sanitizeAlmId(component.alm.key)} | |||
className="text-text-top spacer-left" | |||
height={16} | |||
src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(component.alm.key)}.svg`} | |||
width={16} | |||
/> | |||
</a> | |||
)} | |||
{props.currentBranchLike && ( | |||
<ComponentNavBranch | |||
branchLikes={props.branchLikes} | |||
component={component} | |||
currentBranchLike={props.currentBranchLike} | |||
// to close dropdown on any location change | |||
location={props.location} | |||
/> | |||
)} | |||
</header> | |||
); | |||
} | |||
function renderBreadcrumbs(breadcrumbs: T.Breadcrumb[], shouldLinkLast: boolean) { | |||
const lastItem = breadcrumbs[breadcrumbs.length - 1]; | |||
return breadcrumbs.map((item, index) => { | |||
return ( | |||
<React.Fragment key={item.key}> | |||
{index === 0 && <QualifierIcon className="spacer-right" qualifier={lastItem.qualifier} />} | |||
{shouldLinkLast || index < breadcrumbs.length - 1 ? ( | |||
<Link | |||
className="navbar-context-header-breadcrumb-link link-base-color link-no-underline" | |||
title={item.name} | |||
to={getProjectUrl(item.key)}> | |||
{item.name} | |||
</Link> | |||
) : ( | |||
<span className="navbar-context-header-breadcrumb-link" title={item.name}> | |||
{item.name} | |||
</span> | |||
<> | |||
<Helmet title={component.name} /> | |||
<header className="display-flex-center flex-shrink"> | |||
<ComponentBreadcrumb component={component} currentBranchLike={currentBranchLike} /> | |||
{currentBranchLike && ( | |||
<> | |||
<BranchLikeNavigation | |||
branchLikes={branchLikes} | |||
component={component} | |||
currentBranchLike={currentBranchLike} | |||
/> | |||
<CurrentBranchLikeMergeInformation currentBranchLike={currentBranchLike} /> | |||
</> | |||
)} | |||
{index < breadcrumbs.length - 1 && <span className="slash-separator" />} | |||
</React.Fragment> | |||
); | |||
}); | |||
</header> | |||
</> | |||
); | |||
} | |||
const mapStateToProps = (state: Store, ownProps: OwnProps): StateProps => ({ | |||
organization: getOrganizationByKey(state, ownProps.component.organization) | |||
}); | |||
export default connect(mapStateToProps)(ComponentNavHeader); | |||
export default React.memo(ComponentNavHeader); |
@@ -0,0 +1,52 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockComponent, mockMainBranch } from '../../../../../helpers/testMocks'; | |||
import { ComponentQualifier } from '../../../../../types/component'; | |||
import { ComponentBreadcrumb } from '../ComponentBreadcrumb'; | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
function shallowRender() { | |||
return shallow( | |||
<ComponentBreadcrumb | |||
component={mockComponent({ | |||
breadcrumbs: [ | |||
{ | |||
key: 'parent-portfolio', | |||
name: 'parent-portfolio', | |||
qualifier: ComponentQualifier.Portfolio | |||
}, | |||
{ | |||
key: 'child-portfolio', | |||
name: 'child-portfolio', | |||
qualifier: ComponentQualifier.SubPortfolio | |||
} | |||
] | |||
})} | |||
currentBranchLike={mockMainBranch()} | |||
/> | |||
); | |||
} |
@@ -1,143 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||
import { isSonarCloud } from '../../../../../helpers/system'; | |||
import { | |||
mockLongLivingBranch, | |||
mockMainBranch, | |||
mockPullRequest, | |||
mockShortLivingBranch | |||
} from '../../../../../helpers/testMocks'; | |||
import { ComponentNavBranch } from '../ComponentNavBranch'; | |||
jest.mock('../../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); | |||
const mainBranch = mockMainBranch(); | |||
const fooBranch = mockLongLivingBranch(); | |||
beforeEach(() => { | |||
(isSonarCloud as jest.Mock).mockImplementation(() => false); | |||
}); | |||
it('renders main branch', () => { | |||
const component = {} as T.Component; | |||
expect( | |||
shallow( | |||
<ComponentNavBranch | |||
appState={{ branchesEnabled: true }} | |||
branchLikes={[mainBranch, fooBranch]} | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('renders short-living branch', () => { | |||
const branch: T.ShortLivingBranch = mockShortLivingBranch({ | |||
status: { qualityGateStatus: 'OK' } | |||
}); | |||
const component = {} as T.Component; | |||
expect( | |||
shallow( | |||
<ComponentNavBranch | |||
appState={{ branchesEnabled: true }} | |||
branchLikes={[branch, fooBranch]} | |||
component={component} | |||
currentBranchLike={branch} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('renders pull request', () => { | |||
const pullRequest = mockPullRequest({ | |||
target: 'feature/foo', | |||
url: 'https://example.com/pull/1234' | |||
}); | |||
const component = {} as T.Component; | |||
expect( | |||
shallow( | |||
<ComponentNavBranch | |||
appState={{ branchesEnabled: true }} | |||
branchLikes={[pullRequest, fooBranch]} | |||
component={component} | |||
currentBranchLike={pullRequest} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('opens menu', () => { | |||
const component = {} as T.Component; | |||
const wrapper = shallow( | |||
<ComponentNavBranch | |||
appState={{ branchesEnabled: true }} | |||
branchLikes={[mainBranch, fooBranch]} | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/> | |||
); | |||
expect(wrapper.find('Toggler').prop('open')).toBe(false); | |||
click(wrapper.find('a')); | |||
expect(wrapper.find('Toggler').prop('open')).toBe(true); | |||
}); | |||
it('renders single branch popup', () => { | |||
const component = {} as T.Component; | |||
const wrapper = shallow( | |||
<ComponentNavBranch | |||
appState={{ branchesEnabled: true }} | |||
branchLikes={[mainBranch]} | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/> | |||
); | |||
expect(wrapper.find('DocTooltip')).toMatchSnapshot(); | |||
}); | |||
it('renders no branch support popup', () => { | |||
const component = {} as T.Component; | |||
const wrapper = shallow( | |||
<ComponentNavBranch | |||
appState={{ branchesEnabled: false }} | |||
branchLikes={[mainBranch, fooBranch]} | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/> | |||
); | |||
expect(wrapper.find('DocTooltip')).toMatchSnapshot(); | |||
}); | |||
it('renders nothing on SonarCloud without branch support', () => { | |||
(isSonarCloud as jest.Mock).mockImplementation(() => true); | |||
const component = {} as T.Component; | |||
const wrapper = shallow( | |||
<ComponentNavBranch | |||
appState={{ branchesEnabled: false }} | |||
branchLikes={[mainBranch]} | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/> | |||
); | |||
expect(wrapper.type()).toBeNull(); | |||
}); |
@@ -1,101 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { elementKeydown } from 'sonar-ui-common/helpers/testUtils'; | |||
import { | |||
mockLongLivingBranch, | |||
mockMainBranch, | |||
mockPullRequest, | |||
mockShortLivingBranch | |||
} from '../../../../../helpers/testMocks'; | |||
import { ComponentNavBranchesMenu } from '../ComponentNavBranchesMenu'; | |||
const component = { key: 'component' } as T.Component; | |||
it('renders list', () => { | |||
expect( | |||
shallow( | |||
<ComponentNavBranchesMenu | |||
branchLikes={[ | |||
mockMainBranch(), | |||
shortBranch('foo'), | |||
mockLongLivingBranch({ name: 'bar' }), | |||
shortBranch('baz', true), | |||
mockPullRequest({ status: { qualityGateStatus: 'OK' }, title: 'qux' }) | |||
]} | |||
component={component} | |||
currentBranchLike={mockMainBranch()} | |||
onClose={jest.fn()} | |||
router={{ push: jest.fn() }} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('searches', () => { | |||
const wrapper = shallow( | |||
<ComponentNavBranchesMenu | |||
branchLikes={[ | |||
mockMainBranch(), | |||
shortBranch('foo'), | |||
shortBranch('foobar'), | |||
mockLongLivingBranch({ name: 'bar' }), | |||
mockLongLivingBranch({ name: 'BARBAZ' }) | |||
]} | |||
component={component} | |||
currentBranchLike={mockMainBranch()} | |||
onClose={jest.fn()} | |||
router={{ push: jest.fn() }} | |||
/> | |||
); | |||
wrapper.setState({ query: 'bar' }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('selects next & previous', () => { | |||
const wrapper = shallow<ComponentNavBranchesMenu>( | |||
<ComponentNavBranchesMenu | |||
branchLikes={[ | |||
mockMainBranch(), | |||
shortBranch('foo'), | |||
shortBranch('foobar'), | |||
mockLongLivingBranch({ name: 'bar' }) | |||
]} | |||
component={component} | |||
currentBranchLike={mockMainBranch()} | |||
onClose={jest.fn()} | |||
router={{ push: jest.fn() }} | |||
/> | |||
); | |||
elementKeydown(wrapper.find('SearchBox'), 40); | |||
wrapper.update(); | |||
expect(wrapper.state().selected).toEqual(shortBranch('foo')); | |||
elementKeydown(wrapper.find('SearchBox'), 40); | |||
wrapper.update(); | |||
expect(wrapper.state().selected).toEqual(shortBranch('foobar')); | |||
elementKeydown(wrapper.find('SearchBox'), 38); | |||
wrapper.update(); | |||
expect(wrapper.state().selected).toEqual(shortBranch('foo')); | |||
}); | |||
function shortBranch(name: string, isOrphan?: true) { | |||
return mockShortLivingBranch({ name, isOrphan, status: { qualityGateStatus: 'OK' } }); | |||
} |
@@ -17,69 +17,27 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { isSonarCloud } from '../../../../../helpers/system'; | |||
import { ComponentNavHeader } from '../ComponentNavHeader'; | |||
jest.mock('../../../../../helpers/system', () => ({ | |||
isSonarCloud: jest.fn().mockReturnValue(false) | |||
})); | |||
const component: T.Component = { | |||
breadcrumbs: [{ key: 'my-project', name: 'My Project', qualifier: 'TRK' }], | |||
key: 'my-project', | |||
name: 'My Project', | |||
organization: 'foo', | |||
qualifier: 'TRK', | |||
visibility: 'public' | |||
}; | |||
const organization: T.Organization = { | |||
key: 'foo', | |||
name: 'The Foo Organization', | |||
projectVisibility: 'public' | |||
}; | |||
import { mockSetOfBranchAndPullRequest } from '../../../../../helpers/mocks/branch-pull-request'; | |||
import { mockComponent } from '../../../../../helpers/testMocks'; | |||
import { ComponentNavHeader, ComponentNavHeaderProps } from '../ComponentNavHeader'; | |||
beforeEach(() => { | |||
(isSonarCloud as jest.Mock<any>).mockReturnValue(false); | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should not render breadcrumbs with one element', () => { | |||
expect( | |||
shallow( | |||
<ComponentNavHeader branchLikes={[]} component={component} currentBranchLike={undefined} /> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should render organization', () => { | |||
(isSonarCloud as jest.Mock<any>).mockReturnValue(true); | |||
expect( | |||
shallow( | |||
<ComponentNavHeader | |||
branchLikes={[]} | |||
component={component} | |||
currentBranchLike={undefined} | |||
organization={organization} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props?: Partial<ComponentNavHeaderProps>) { | |||
const branchLikes = mockSetOfBranchAndPullRequest(); | |||
it('should render alm links', () => { | |||
(isSonarCloud as jest.Mock<any>).mockReturnValue(true); | |||
expect( | |||
shallow( | |||
<ComponentNavHeader | |||
branchLikes={[]} | |||
component={{ | |||
...component, | |||
alm: { key: 'bitbucketcloud', url: 'https://bitbucket.org/foo' } | |||
}} | |||
currentBranchLike={undefined} | |||
organization={organization} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
return shallow( | |||
<ComponentNavHeader | |||
branchLikes={branchLikes} | |||
component={mockComponent()} | |||
currentBranchLike={branchLikes[0]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,48 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="big flex-shrink display-flex-center" | |||
> | |||
<span | |||
className="flex-shrink display-flex-center" | |||
key="parent-portfolio" | |||
> | |||
<QualifierIcon | |||
className="spacer-right" | |||
qualifier="SVW" | |||
/> | |||
<Link | |||
className="link-no-underline text-ellipsis" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
title="parent-portfolio" | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "parent-portfolio", | |||
}, | |||
} | |||
} | |||
> | |||
parent-portfolio | |||
</Link> | |||
<span | |||
className="slash-separator" | |||
/> | |||
</span> | |||
<span | |||
className="flex-shrink display-flex-center" | |||
key="child-portfolio" | |||
> | |||
<span | |||
className="text-ellipsis" | |||
title="child-portfolio" | |||
> | |||
child-portfolio | |||
</span> | |||
</span> | |||
</div> | |||
`; |
@@ -8,7 +8,7 @@ exports[`renders 1`] = ` | |||
<div | |||
className="navbar-context-justified" | |||
> | |||
<Connect(ComponentNavHeader) | |||
<Memo(ComponentNavHeader) | |||
branchLikes={Array []} | |||
component={ | |||
Object { | |||
@@ -25,7 +25,6 @@ exports[`renders 1`] = ` | |||
"qualifier": "TRK", | |||
} | |||
} | |||
location={Object {}} | |||
/> | |||
<Connect(ComponentNavMeta) | |||
component={ |
@@ -1,286 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders main branch 1`] = ` | |||
<div | |||
className="navbar-context-branches" | |||
> | |||
<div | |||
className="dropdown" | |||
> | |||
<Toggler | |||
onRequestClose={[Function]} | |||
open={false} | |||
overlay={ | |||
<withRouter(ComponentNavBranchesMenu) | |||
branchLikes={ | |||
Array [ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
"type": "LONG", | |||
}, | |||
] | |||
} | |||
component={Object {}} | |||
currentBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
onClose={[Function]} | |||
/> | |||
} | |||
> | |||
<a | |||
className="link-base-color link-no-underline nowrap" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<BranchIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
<span | |||
className="text-limited text-top" | |||
title="master" | |||
> | |||
master | |||
</span> | |||
<DropdownIcon | |||
className="little-spacer-left" | |||
/> | |||
</a> | |||
</Toggler> | |||
</div> | |||
</div> | |||
`; | |||
exports[`renders no branch support popup 1`] = ` | |||
<DocTooltip | |||
className="spacer-left" | |||
doc={Promise {}} | |||
> | |||
<PlusCircleIcon | |||
fill="#b4b4b4" | |||
size={12} | |||
/> | |||
</DocTooltip> | |||
`; | |||
exports[`renders pull request 1`] = ` | |||
<div | |||
className="navbar-context-branches" | |||
> | |||
<div | |||
className="dropdown" | |||
> | |||
<Toggler | |||
onRequestClose={[Function]} | |||
open={false} | |||
overlay={ | |||
<withRouter(ComponentNavBranchesMenu) | |||
branchLikes={ | |||
Array [ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "feature/foo", | |||
"title": "Foo Bar feature", | |||
"url": "https://example.com/pull/1234", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
"type": "LONG", | |||
}, | |||
] | |||
} | |||
component={Object {}} | |||
currentBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "feature/foo", | |||
"title": "Foo Bar feature", | |||
"url": "https://example.com/pull/1234", | |||
} | |||
} | |||
onClose={[Function]} | |||
/> | |||
} | |||
> | |||
<a | |||
className="link-base-color link-no-underline nowrap" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<BranchIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "feature/foo", | |||
"title": "Foo Bar feature", | |||
"url": "https://example.com/pull/1234", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
<span | |||
className="text-limited text-top" | |||
title="1001 – Foo Bar feature" | |||
> | |||
1001 – Foo Bar feature | |||
</span> | |||
<DropdownIcon | |||
className="little-spacer-left" | |||
/> | |||
</a> | |||
</Toggler> | |||
</div> | |||
<span | |||
className="note big-spacer-left text-ellipsis flex-shrink" | |||
> | |||
<FormattedMessage | |||
defaultMessage="branches.pull_request.for_merge_into_x_from_y" | |||
id="branches.pull_request.for_merge_into_x_from_y" | |||
values={ | |||
Object { | |||
"branch": <strong> | |||
feature/foo/bar | |||
</strong>, | |||
"target": <strong> | |||
feature/foo | |||
</strong>, | |||
} | |||
} | |||
/> | |||
</span> | |||
</div> | |||
`; | |||
exports[`renders short-living branch 1`] = ` | |||
<div | |||
className="navbar-context-branches" | |||
> | |||
<div | |||
className="dropdown" | |||
> | |||
<Toggler | |||
onRequestClose={[Function]} | |||
open={false} | |||
overlay={ | |||
<withRouter(ComponentNavBranchesMenu) | |||
branchLikes={ | |||
Array [ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "feature/foo", | |||
"status": Object { | |||
"qualityGateStatus": "OK", | |||
}, | |||
"type": "SHORT", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
"type": "LONG", | |||
}, | |||
] | |||
} | |||
component={Object {}} | |||
currentBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "feature/foo", | |||
"status": Object { | |||
"qualityGateStatus": "OK", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
onClose={[Function]} | |||
/> | |||
} | |||
> | |||
<a | |||
className="link-base-color link-no-underline nowrap" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<BranchIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "feature/foo", | |||
"status": Object { | |||
"qualityGateStatus": "OK", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
<span | |||
className="text-limited text-top" | |||
title="feature/foo" | |||
> | |||
feature/foo | |||
</span> | |||
<DropdownIcon | |||
className="little-spacer-left" | |||
/> | |||
</a> | |||
</Toggler> | |||
</div> | |||
<span | |||
className="note big-spacer-left" | |||
> | |||
from | |||
<strong> | |||
master | |||
</strong> | |||
</span> | |||
</div> | |||
`; | |||
exports[`renders single branch popup 1`] = ` | |||
<DocTooltip | |||
className="spacer-left" | |||
doc={Promise {}} | |||
> | |||
<PlusCircleIcon | |||
fill="#4b9fd5" | |||
size={12} | |||
/> | |||
</DocTooltip> | |||
`; |
@@ -1,291 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders list 1`] = ` | |||
<DropdownOverlay | |||
noPadding={true} | |||
> | |||
<div | |||
className="menu-search" | |||
> | |||
<SearchBox | |||
autoFocus={true} | |||
onChange={[Function]} | |||
onKeyDown={[Function]} | |||
placeholder="branches.search_for_branches" | |||
value="" | |||
/> | |||
</div> | |||
<ul | |||
className="menu menu-vertically-limited" | |||
> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
innerRef={[Function]} | |||
key="branch-master" | |||
onSelect={[Function]} | |||
selected={true} | |||
/> | |||
<li | |||
className="menu-header navbar-context-meta-branch-menu-title" | |||
> | |||
branches.pull_requests | |||
</li> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"status": Object { | |||
"qualityGateStatus": "OK", | |||
}, | |||
"target": "master", | |||
"title": "qux", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
innerRef={[Function]} | |||
key="pull-request-1001" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
<li | |||
className="divider" | |||
/> | |||
<li | |||
className="menu-header" | |||
> | |||
<div | |||
className="display-inline-block text-middle" | |||
> | |||
branches.orphan_branches | |||
</div> | |||
<HelpTooltip | |||
className="spacer-left" | |||
overlay="branches.orphan_branches.tooltip" | |||
/> | |||
</li> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"isOrphan": true, | |||
"mergeBranch": "master", | |||
"name": "baz", | |||
"status": Object { | |||
"qualityGateStatus": "OK", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
innerRef={[Function]} | |||
key="branch-baz" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"isOrphan": undefined, | |||
"mergeBranch": "master", | |||
"name": "foo", | |||
"status": Object { | |||
"qualityGateStatus": "OK", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
innerRef={[Function]} | |||
key="branch-foo" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
<li | |||
className="divider" | |||
/> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "bar", | |||
"type": "LONG", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
innerRef={[Function]} | |||
key="branch-bar" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
<li | |||
className="divider" | |||
/> | |||
<li | |||
className="menu-header" | |||
> | |||
<div | |||
className="display-inline-block text-middle" | |||
> | |||
branches.orphan_branches | |||
</div> | |||
<HelpTooltip | |||
className="spacer-left" | |||
overlay="branches.orphan_branches.tooltip" | |||
/> | |||
</li> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"isOrphan": true, | |||
"mergeBranch": "master", | |||
"name": "baz", | |||
"status": Object { | |||
"qualityGateStatus": "OK", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
innerRef={[Function]} | |||
key="branch-baz" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
</ul> | |||
</DropdownOverlay> | |||
`; | |||
exports[`searches 1`] = ` | |||
<DropdownOverlay | |||
noPadding={true} | |||
> | |||
<div | |||
className="menu-search" | |||
> | |||
<SearchBox | |||
autoFocus={true} | |||
onChange={[Function]} | |||
onKeyDown={[Function]} | |||
placeholder="branches.search_for_branches" | |||
value="bar" | |||
/> | |||
</div> | |||
<ul | |||
className="menu menu-vertically-limited" | |||
> | |||
<li | |||
className="menu-header navbar-context-meta-branch-menu-title" | |||
> | |||
branches.short_lived_branches | |||
</li> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"isOrphan": undefined, | |||
"mergeBranch": "master", | |||
"name": "foobar", | |||
"status": Object { | |||
"qualityGateStatus": "OK", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
innerRef={[Function]} | |||
key="branch-foobar" | |||
onSelect={[Function]} | |||
selected={true} | |||
/> | |||
<li | |||
className="divider" | |||
/> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "BARBAZ", | |||
"type": "LONG", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
innerRef={[Function]} | |||
key="branch-BARBAZ" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
<li | |||
className="divider" | |||
/> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "bar", | |||
"type": "LONG", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
innerRef={[Function]} | |||
key="branch-bar" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
</ul> | |||
</DropdownOverlay> | |||
`; |
@@ -1,181 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders main branch 1`] = ` | |||
<li | |||
key="branch-master" | |||
onMouseEnter={[Function]} | |||
> | |||
<Link | |||
className="navbar-context-meta-branch-menu-item" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "component", | |||
}, | |||
} | |||
} | |||
> | |||
<div | |||
className="navbar-context-meta-branch-menu-item-name text-ellipsis" | |||
title="master" | |||
> | |||
<BranchIcon | |||
branchLike={ | |||
Object { | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
master | |||
<div | |||
className="badge spacer-left" | |||
> | |||
branches.main_branch | |||
</div> | |||
</div> | |||
<div | |||
className="big-spacer-left note" | |||
> | |||
<Connect(BranchStatus) | |||
branchLike={ | |||
Object { | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component="component" | |||
/> | |||
</div> | |||
</Link> | |||
</li> | |||
`; | |||
exports[`renders short-living branch 1`] = ` | |||
<li | |||
key="branch-foo" | |||
onMouseEnter={[Function]} | |||
> | |||
<Link | |||
className="navbar-context-meta-branch-menu-item" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": "foo", | |||
"id": "component", | |||
}, | |||
} | |||
} | |||
> | |||
<div | |||
className="navbar-context-meta-branch-menu-item-name text-ellipsis" | |||
title="foo" | |||
> | |||
<BranchIcon | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "foo", | |||
"status": Object { | |||
"qualityGateStatus": "ERROR", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
className="little-spacer-right big-spacer-left" | |||
/> | |||
foo | |||
</div> | |||
<div | |||
className="big-spacer-left note" | |||
> | |||
<Connect(BranchStatus) | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "foo", | |||
"status": Object { | |||
"qualityGateStatus": "ERROR", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
component="component" | |||
/> | |||
</div> | |||
</Link> | |||
</li> | |||
`; | |||
exports[`renders short-living orhpan branch 1`] = ` | |||
<li | |||
key="branch-foo" | |||
onMouseEnter={[Function]} | |||
> | |||
<Link | |||
className="navbar-context-meta-branch-menu-item" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": "foo", | |||
"id": "component", | |||
}, | |||
} | |||
} | |||
> | |||
<div | |||
className="navbar-context-meta-branch-menu-item-name text-ellipsis" | |||
title="foo" | |||
> | |||
<BranchIcon | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"isOrphan": true, | |||
"mergeBranch": "master", | |||
"name": "foo", | |||
"status": Object { | |||
"qualityGateStatus": "ERROR", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
foo | |||
</div> | |||
<div | |||
className="big-spacer-left note" | |||
> | |||
<Connect(BranchStatus) | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"isOrphan": true, | |||
"mergeBranch": "master", | |||
"name": "foo", | |||
"status": Object { | |||
"qualityGateStatus": "ERROR", | |||
}, | |||
"type": "SHORT", | |||
} | |||
} | |||
component="component" | |||
/> | |||
</div> | |||
</Link> | |||
</li> | |||
`; |
@@ -1,137 +1,160 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should not render breadcrumbs with one element 1`] = ` | |||
<header | |||
className="navbar-context-header" | |||
> | |||
<OrganizationHelmet | |||
title="My Project" | |||
exports[`should render correctly 1`] = ` | |||
<Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="MyProject" | |||
/> | |||
<QualifierIcon | |||
className="spacer-right" | |||
qualifier="TRK" | |||
/> | |||
<span | |||
className="navbar-context-header-breadcrumb-link" | |||
title="My Project" | |||
<header | |||
className="display-flex-center flex-shrink" | |||
> | |||
My Project | |||
</span> | |||
</header> | |||
`; | |||
exports[`should render alm links 1`] = ` | |||
<header | |||
className="navbar-context-header" | |||
> | |||
<OrganizationHelmet | |||
organization={ | |||
Object { | |||
"key": "foo", | |||
"name": "The Foo Organization", | |||
"projectVisibility": "public", | |||
<ComponentBreadcrumb | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
} | |||
title="My Project" | |||
/> | |||
<OrganizationAvatar | |||
organization={ | |||
Object { | |||
"key": "foo", | |||
"name": "The Foo Organization", | |||
"projectVisibility": "public", | |||
} | |||
} | |||
/> | |||
<OrganizationLink | |||
className="navbar-context-header-breadcrumb-link link-base-color link-no-underline spacer-left" | |||
organization={ | |||
Object { | |||
"key": "foo", | |||
"name": "The Foo Organization", | |||
"projectVisibility": "public", | |||
currentBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
} | |||
} | |||
} | |||
> | |||
The Foo Organization | |||
</OrganizationLink> | |||
<span | |||
className="slash-separator" | |||
/> | |||
<QualifierIcon | |||
className="spacer-right" | |||
qualifier="TRK" | |||
/> | |||
<span | |||
className="navbar-context-header-breadcrumb-link" | |||
title="My Project" | |||
> | |||
My Project | |||
</span> | |||
<a | |||
className="link-no-underline" | |||
href="https://bitbucket.org/foo" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
<img | |||
alt="bitbucket" | |||
className="text-text-top spacer-left" | |||
height={16} | |||
src="/images/sonarcloud/bitbucket.svg" | |||
width={16} | |||
/> | |||
</a> | |||
</header> | |||
`; | |||
exports[`should render organization 1`] = ` | |||
<header | |||
className="navbar-context-header" | |||
> | |||
<OrganizationHelmet | |||
organization={ | |||
Object { | |||
"key": "foo", | |||
"name": "The Foo Organization", | |||
"projectVisibility": "public", | |||
<Connect(withAppState(Component)) | |||
branchLikes={ | |||
Array [ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-1", | |||
"type": "LONG", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1", | |||
"target": "master", | |||
"title": "PR-1", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "llb-1", | |||
"name": "slb-2", | |||
"type": "SHORT", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "2", | |||
"target": "master", | |||
"title": "PR-2", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-3", | |||
"type": "LONG", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-2", | |||
"type": "LONG", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"isOrphan": true, | |||
"key": "2", | |||
"target": "llb-100", | |||
"title": "PR-2", | |||
}, | |||
] | |||
} | |||
} | |||
title="My Project" | |||
/> | |||
<OrganizationAvatar | |||
organization={ | |||
Object { | |||
"key": "foo", | |||
"name": "The Foo Organization", | |||
"projectVisibility": "public", | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
} | |||
/> | |||
<OrganizationLink | |||
className="navbar-context-header-breadcrumb-link link-base-color link-no-underline spacer-left" | |||
organization={ | |||
Object { | |||
"key": "foo", | |||
"name": "The Foo Organization", | |||
"projectVisibility": "public", | |||
currentBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
} | |||
} | |||
} | |||
> | |||
The Foo Organization | |||
</OrganizationLink> | |||
<span | |||
className="slash-separator" | |||
/> | |||
<QualifierIcon | |||
className="spacer-right" | |||
qualifier="TRK" | |||
/> | |||
<span | |||
className="navbar-context-header-breadcrumb-link" | |||
title="My Project" | |||
> | |||
My Project | |||
</span> | |||
</header> | |||
/> | |||
<Memo(CurrentBranchLikeMergeInformation) | |||
currentBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
} | |||
} | |||
/> | |||
</header> | |||
</Fragment> | |||
`; |
@@ -0,0 +1,59 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
.branch-like-navigation-toggler-container .popup { | |||
min-width: 430px; | |||
max-width: 650px; | |||
} | |||
.branch-like-navigation-menu .search-box-container { | |||
padding: var(--gridSize); | |||
} | |||
.branch-like-navigation-menu .search-box-container .search-box, | |||
.branch-like-navigation-menu .search-box-container .search-box-input { | |||
max-width: initial !important; | |||
} | |||
.branch-like-navigation-menu .item-list { | |||
padding-bottom: var(--gridSize); | |||
max-height: 300px; | |||
overflow-y: auto; | |||
} | |||
.branch-like-navigation-menu .item { | |||
padding: calc(var(--gridSize) / 2) var(--gridSize); | |||
} | |||
.branch-like-navigation-menu .item.header { | |||
color: var(--secondFontColor); | |||
} | |||
.branch-like-navigation-menu .item:not(.header):hover, | |||
.branch-like-navigation-menu .item:not(.header).active { | |||
background-color: var(--barBackgroundColor); | |||
cursor: pointer; | |||
} | |||
.branch-like-navigation-menu .hint-container { | |||
padding: var(--gridSize); | |||
background-color: var(--barBackgroundColor); | |||
border-top: 1px solid var(--barBorderColor); | |||
} |
@@ -0,0 +1,93 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import Toggler from 'sonar-ui-common/components/controls/Toggler'; | |||
import { withAppState } from '../../../../../components/hoc/withAppState'; | |||
import './BranchLikeNavigation.css'; | |||
import CurrentBranchLike from './CurrentBranchLike'; | |||
import Menu from './Menu'; | |||
export interface BranchLikeNavigationProps { | |||
appState: Pick<T.AppState, 'branchesEnabled'>; | |||
branchLikes: T.BranchLike[]; | |||
component: T.Component; | |||
currentBranchLike: T.BranchLike; | |||
} | |||
export function BranchLikeNavigation(props: BranchLikeNavigationProps) { | |||
const { | |||
appState: { branchesEnabled }, | |||
branchLikes, | |||
component, | |||
component: { configuration }, | |||
currentBranchLike | |||
} = props; | |||
const [isMenuOpen, setIsMenuOpen] = React.useState(false); | |||
const canAdminComponent = configuration && configuration.showSettings; | |||
const hasManyBranches = branchLikes.length >= 2; | |||
const isMenuEnabled = branchesEnabled && hasManyBranches; | |||
const currentBranchLikeElement = ( | |||
<CurrentBranchLike | |||
branchesEnabled={Boolean(branchesEnabled)} | |||
component={component} | |||
currentBranchLike={currentBranchLike} | |||
hasManyBranches={hasManyBranches} | |||
/> | |||
); | |||
return ( | |||
<span | |||
className={classNames( | |||
'big-spacer-left flex-shrink branch-like-navigation-toggler-container', | |||
{ dropdown: isMenuEnabled } | |||
)}> | |||
{isMenuEnabled ? ( | |||
<Toggler | |||
onRequestClose={() => setIsMenuOpen(false)} | |||
open={isMenuOpen} | |||
overlay={ | |||
<Menu | |||
branchLikes={branchLikes} | |||
canAdminComponent={canAdminComponent} | |||
component={component} | |||
currentBranchLike={currentBranchLike} | |||
onClose={() => setIsMenuOpen(false)} | |||
/> | |||
}> | |||
<a | |||
className="link-base-color link-no-underline" | |||
href="#" | |||
onClick={() => setIsMenuOpen(!isMenuOpen)}> | |||
{currentBranchLikeElement} | |||
</a> | |||
</Toggler> | |||
) : ( | |||
currentBranchLikeElement | |||
)} | |||
</span> | |||
); | |||
} | |||
export default withAppState(React.memo(BranchLikeNavigation)); |
@@ -0,0 +1,114 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { 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 DocTooltip from '../../../../../components/docs/DocTooltip'; | |||
import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon'; | |||
import { getBranchLikeDisplayName } from '../../../../../helpers/branches'; | |||
import { getPortfolioAdminUrl } from '../../../../../helpers/urls'; | |||
import { ComponentQualifier } from '../../../../../types/component'; | |||
import { colors } from '../../../../theme'; | |||
export interface CurrentBranchLikeProps { | |||
branchesEnabled: boolean; | |||
component: T.Component; | |||
currentBranchLike: T.BranchLike; | |||
hasManyBranches: boolean; | |||
} | |||
export function CurrentBranchLike(props: CurrentBranchLikeProps) { | |||
const { | |||
branchesEnabled, | |||
component, | |||
component: { configuration }, | |||
currentBranchLike, | |||
hasManyBranches | |||
} = props; | |||
const displayName = getBranchLikeDisplayName(currentBranchLike); | |||
const isApplication = component.qualifier === ComponentQualifier.Application; | |||
const canAdminComponent = configuration && configuration.showSettings; | |||
const additionalIcon = () => { | |||
const plusIcon = <PlusCircleIcon fill={colors.blue} size={12} />; | |||
if (branchesEnabled && hasManyBranches) { | |||
return <DropdownIcon />; | |||
} | |||
if (isApplication) { | |||
if (!hasManyBranches && canAdminComponent) { | |||
return ( | |||
<HelpTooltip | |||
overlay={ | |||
<> | |||
<p>{translate('application.branches.help')}</p> | |||
<hr className="spacer-top spacer-bottom" /> | |||
<Link to={getPortfolioAdminUrl(component.key, component.qualifier)}> | |||
{translate('application.branches.link')} | |||
</Link> | |||
</> | |||
}> | |||
{plusIcon} | |||
</HelpTooltip> | |||
); | |||
} | |||
} else { | |||
if (!branchesEnabled) { | |||
return ( | |||
<DocTooltip | |||
data-test="branches-support-disabled" | |||
doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/branches/no-branch-support.md')}> | |||
{plusIcon} | |||
</DocTooltip> | |||
); | |||
} | |||
if (!hasManyBranches) { | |||
return ( | |||
<DocTooltip | |||
data-test="only-one-branch-like" | |||
doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/branches/single-branch.md')}> | |||
{plusIcon} | |||
</DocTooltip> | |||
); | |||
} | |||
} | |||
return null; | |||
}; | |||
return ( | |||
<span className="display-flex-center flex-shrink text-ellipsis"> | |||
<BranchLikeIcon branchLike={currentBranchLike} /> | |||
<span className="spacer-left spacer-right flex-shrink text-ellipsis" title={displayName}> | |||
{displayName} | |||
</span> | |||
{additionalIcon()} | |||
</span> | |||
); | |||
} | |||
export default React.memo(CurrentBranchLike); |
@@ -0,0 +1,51 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { isPullRequest } from '../../../../../helpers/branches'; | |||
export interface CurrentBranchLikeMergeInformationProps { | |||
currentBranchLike: T.BranchLike; | |||
} | |||
export function CurrentBranchLikeMergeInformation(props: CurrentBranchLikeMergeInformationProps) { | |||
const { currentBranchLike } = props; | |||
if (!isPullRequest(currentBranchLike)) { | |||
return null; | |||
} | |||
return ( | |||
<span className="big-spacer-left flex-shrink note text-ellipsis"> | |||
<FormattedMessage | |||
defaultMessage={translate('branch_like_navigation.for_merge_into_x_from_y')} | |||
id="branch_like_navigation.for_merge_into_x_from_y" | |||
values={{ | |||
target: <strong>{currentBranchLike.target}</strong>, | |||
branch: <strong>{currentBranchLike.branch}</strong> | |||
}} | |||
/> | |||
</span> | |||
); | |||
} | |||
export default React.memo(CurrentBranchLikeMergeInformation); |
@@ -0,0 +1,202 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown'; | |||
import SearchBox from 'sonar-ui-common/components/controls/SearchBox'; | |||
import { KeyCodes } from 'sonar-ui-common/helpers/keycodes'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { Router, withRouter } from '../../../../../components/hoc/withRouter'; | |||
import { | |||
getBrancheLikesAsTree, | |||
isBranch, | |||
isPullRequest, | |||
isSameBranchLike | |||
} from '../../../../../helpers/branches'; | |||
import { getBranchLikeUrl } from '../../../../../helpers/urls'; | |||
import { ComponentQualifier } from '../../../../../types/component'; | |||
import MenuItemList from './MenuItemList'; | |||
interface Props { | |||
branchLikes: T.BranchLike[]; | |||
canAdminComponent?: boolean; | |||
component: T.Component; | |||
currentBranchLike: T.BranchLike; | |||
onClose: () => void; | |||
router: Pick<Router, 'push'>; | |||
} | |||
interface State { | |||
branchLikesToDisplay: T.BranchLike[]; | |||
branchLikesToDisplayTree: T.BranchLikeTree; | |||
query: string; | |||
selectedBranchLike: T.BranchLike | undefined; | |||
} | |||
export class Menu extends React.PureComponent<Props, State> { | |||
constructor(props: Props) { | |||
super(props); | |||
let selectedBranchLike = undefined; | |||
if (props.branchLikes.some(b => isSameBranchLike(b, props.currentBranchLike))) { | |||
selectedBranchLike = props.currentBranchLike; | |||
} else if (props.branchLikes.length > 0) { | |||
selectedBranchLike = props.branchLikes[0]; | |||
} | |||
this.state = { | |||
query: '', | |||
selectedBranchLike, | |||
...this.processBranchLikes(props.branchLikes) | |||
}; | |||
} | |||
processBranchLikes = (branchLikes: T.BranchLike[]) => { | |||
const tree = getBrancheLikesAsTree(branchLikes); | |||
return { | |||
branchLikesToDisplay: [ | |||
...(tree.mainBranchTree | |||
? [tree.mainBranchTree.branch, ...tree.mainBranchTree.pullRequests] | |||
: []), | |||
...tree.branchTree.reduce((prev, t) => [...prev, t.branch, ...t.pullRequests], []), | |||
...tree.parentlessPullRequests, | |||
...tree.orphanPullRequests | |||
], | |||
branchLikesToDisplayTree: tree | |||
}; | |||
}; | |||
openHighlightedBranchLike = () => { | |||
if (this.state.selectedBranchLike) { | |||
this.handleOnSelect(this.state.selectedBranchLike); | |||
} | |||
}; | |||
highlightSiblingBranchlike = (indexDelta: number) => { | |||
const selectBranchLikeIndex = this.state.branchLikesToDisplay.findIndex(b => | |||
isSameBranchLike(b, this.state.selectedBranchLike) | |||
); | |||
const newIndex = selectBranchLikeIndex + indexDelta; | |||
if ( | |||
selectBranchLikeIndex !== -1 && | |||
newIndex >= 0 && | |||
newIndex < this.state.branchLikesToDisplay.length | |||
) { | |||
this.setState(({ branchLikesToDisplay }) => ({ | |||
selectedBranchLike: branchLikesToDisplay[newIndex] | |||
})); | |||
} | |||
}; | |||
handleKeyDown = (event: React.KeyboardEvent) => { | |||
switch (event.keyCode) { | |||
case KeyCodes.Enter: | |||
event.preventDefault(); | |||
this.openHighlightedBranchLike(); | |||
break; | |||
case KeyCodes.UpArrow: | |||
event.preventDefault(); | |||
this.highlightSiblingBranchlike(-1); | |||
break; | |||
case KeyCodes.DownArrow: | |||
event.preventDefault(); | |||
this.highlightSiblingBranchlike(+1); | |||
break; | |||
} | |||
}; | |||
handleSearchChange = (query: string) => { | |||
const q = query.toLowerCase(); | |||
const filterBranch = (branch: T.BranchLike) => | |||
isBranch(branch) && branch.name.toLowerCase().includes(q); | |||
const filterPullRequest = (pr: T.BranchLike) => | |||
isPullRequest(pr) && (pr.title.toLowerCase().includes(q) || pr.key.toLowerCase().includes(q)); | |||
const filteredBranchLikes = this.props.branchLikes.filter( | |||
bl => filterBranch(bl) || filterPullRequest(bl) | |||
); | |||
this.setState({ | |||
query: q, | |||
selectedBranchLike: filteredBranchLikes.length > 0 ? filteredBranchLikes[0] : undefined, | |||
...this.processBranchLikes(filteredBranchLikes) | |||
}); | |||
}; | |||
handleOnSelect = (branchLike: T.BranchLike) => { | |||
this.setState({ selectedBranchLike: branchLike }, () => { | |||
this.props.onClose(); | |||
this.props.router.push(getBranchLikeUrl(this.props.component.key, branchLike)); | |||
}); | |||
}; | |||
render() { | |||
const { canAdminComponent, component, onClose } = this.props; | |||
const { | |||
branchLikesToDisplay, | |||
branchLikesToDisplayTree, | |||
query, | |||
selectedBranchLike | |||
} = this.state; | |||
const showManageLink = component.qualifier === ComponentQualifier.Project && canAdminComponent; | |||
const hasResults = branchLikesToDisplay.length > 0; | |||
return ( | |||
<DropdownOverlay className="branch-like-navigation-menu" noPadding={true}> | |||
<div className="search-box-container"> | |||
<SearchBox | |||
autoFocus={true} | |||
onChange={this.handleSearchChange} | |||
onKeyDown={this.handleKeyDown} | |||
placeholder={translate('branch_like_navigation.search_for_branch_like')} | |||
value={query} | |||
/> | |||
</div> | |||
<div className="item-list-container"> | |||
<MenuItemList | |||
branchLikeTree={branchLikesToDisplayTree} | |||
component={component} | |||
hasResults={hasResults} | |||
onSelect={this.handleOnSelect} | |||
selectedBranchLike={selectedBranchLike} | |||
/> | |||
</div> | |||
{showManageLink && ( | |||
<div className="hint-container text-right"> | |||
<Link | |||
onClick={() => onClose()} | |||
to={{ pathname: '/project/branches', query: { id: component.key } }}> | |||
{translate('branch_like_navigation.manage')} | |||
</Link> | |||
</div> | |||
)} | |||
</DropdownOverlay> | |||
); | |||
} | |||
} | |||
export default withRouter(Menu); |
@@ -0,0 +1,67 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import BranchStatus from '../../../../../components/common/BranchStatus'; | |||
import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon'; | |||
import { getBranchLikeDisplayName, isMainBranch } from '../../../../../helpers/branches'; | |||
export interface MenuItemProps { | |||
branchLike: T.BranchLike; | |||
component: T.Component; | |||
indent?: boolean; | |||
onSelect: (branchLike: T.BranchLike) => void; | |||
selected: boolean; | |||
setSelectedNode?: (node: HTMLLIElement) => void; | |||
} | |||
export function MenuItem(props: MenuItemProps) { | |||
const { branchLike, component, indent, setSelectedNode, onSelect, selected } = props; | |||
const displayName = getBranchLikeDisplayName(branchLike); | |||
return ( | |||
<li | |||
className={classNames('item', { | |||
active: selected | |||
})} | |||
onClick={() => onSelect(branchLike)} | |||
ref={selected ? setSelectedNode : undefined}> | |||
<div | |||
className={classNames('display-flex-center display-flex-space-between', { | |||
'big-spacer-left': indent | |||
})}> | |||
<div className="item-name text-ellipsis" title={displayName}> | |||
<BranchLikeIcon branchLike={branchLike} /> | |||
<span className="spacer-left">{displayName}</span> | |||
{isMainBranch(branchLike) && ( | |||
<span className="badge spacer-left">{translate('branches.main_branch')}</span> | |||
)} | |||
</div> | |||
<div className="spacer-left"> | |||
<BranchStatus branchLike={branchLike} component={component.key} /> | |||
</div> | |||
</div> | |||
</li> | |||
); | |||
} | |||
export default React.memo(MenuItem); |
@@ -0,0 +1,112 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; | |||
import { isDefined } from 'sonar-ui-common/helpers/types'; | |||
import { getBranchLikeKey, isSameBranchLike } from '../../../../../helpers/branches'; | |||
import MenuItem from './MenuItem'; | |||
export interface MenuItemListProps { | |||
branchLikeTree: T.BranchLikeTree; | |||
component: T.Component; | |||
hasResults: boolean; | |||
onSelect: (branchLike: T.BranchLike) => void; | |||
selectedBranchLike: T.BranchLike | undefined; | |||
} | |||
export function MenuItemList(props: MenuItemListProps) { | |||
let listNode: HTMLUListElement | null = null; | |||
let selectedNode: HTMLLIElement | null = null; | |||
React.useEffect(() => { | |||
if (listNode && selectedNode) { | |||
scrollToElement(selectedNode, { parent: listNode, smooth: false }); | |||
} | |||
}); | |||
const { branchLikeTree, component, hasResults, onSelect, selectedBranchLike } = props; | |||
const renderItem = (branchLike: T.BranchLike, indent?: boolean) => ( | |||
<MenuItem | |||
branchLike={branchLike} | |||
component={component} | |||
indent={indent} | |||
key={getBranchLikeKey(branchLike)} | |||
onSelect={onSelect} | |||
selected={isSameBranchLike(branchLike, selectedBranchLike)} | |||
setSelectedNode={node => (selectedNode = node)} | |||
/> | |||
); | |||
return ( | |||
<ul className="item-list" ref={node => (listNode = node)}> | |||
{!hasResults && ( | |||
<li className="item"> | |||
<span className="note">{translate('no_results')}</span> | |||
</li> | |||
)} | |||
{/* BRANCHES & PR */} | |||
{[branchLikeTree.mainBranchTree, ...branchLikeTree.branchTree].filter(isDefined).map(tree => ( | |||
<React.Fragment key={getBranchLikeKey(tree.branch)}> | |||
{renderItem(tree.branch)} | |||
{tree.pullRequests.length > 0 && ( | |||
<> | |||
<li className="item header"> | |||
<span className="big-spacer-left"> | |||
{translate('branch_like_navigation.pull_requests')} | |||
</span> | |||
</li> | |||
{tree.pullRequests.map(pr => renderItem(pr, true))} | |||
</> | |||
)} | |||
<hr /> | |||
</React.Fragment> | |||
))} | |||
{/* PARENTLESS PR (for display during search) */} | |||
{branchLikeTree.parentlessPullRequests.length > 0 && ( | |||
<> | |||
<li className="item header">{translate('branch_like_navigation.pull_requests')}</li> | |||
{branchLikeTree.parentlessPullRequests.map(pr => renderItem(pr))} | |||
</> | |||
)} | |||
{/* ORPHAN PR */} | |||
{branchLikeTree.orphanPullRequests.length > 0 && ( | |||
<> | |||
<li className="item header"> | |||
{translate('branch_like_navigation.orphan_pull_requests')} | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay={translate('branch_like_navigation.orphan_pull_requests.tooltip')} | |||
/> | |||
</li> | |||
{branchLikeTree.orphanPullRequests.map(pr => renderItem(pr))} | |||
</> | |||
)} | |||
</ul> | |||
); | |||
} | |||
export default React.memo(MenuItemList); |
@@ -0,0 +1,76 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import Toggler from 'sonar-ui-common/components/controls/Toggler'; | |||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||
import { mockSetOfBranchAndPullRequest } from '../../../../../../helpers/mocks/branch-pull-request'; | |||
import { mockAppState, mockComponent } from '../../../../../../helpers/testMocks'; | |||
import { BranchLikeNavigation, BranchLikeNavigationProps } from '../BranchLikeNavigation'; | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should render the menu trigger if branches are enabled', () => { | |||
const wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: true }) }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should properly toggle menu opening when clicking the anchor', () => { | |||
const wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: true }) }); | |||
expect(wrapper.find(Toggler).props().open).toBe(false); | |||
click(wrapper.find('a')); | |||
expect(wrapper.find(Toggler).props().open).toBe(true); | |||
click(wrapper.find('a')); | |||
expect(wrapper.find(Toggler).props().open).toBe(false); | |||
}); | |||
it('should properly close menu when toggler asks for', () => { | |||
const wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: true }) }); | |||
expect(wrapper.find(Toggler).props().open).toBe(false); | |||
click(wrapper.find('a')); | |||
expect(wrapper.find(Toggler).props().open).toBe(true); | |||
wrapper | |||
.find(Toggler) | |||
.props() | |||
.onRequestClose(); | |||
expect(wrapper.find(Toggler).props().open).toBe(false); | |||
}); | |||
function shallowRender(props?: Partial<BranchLikeNavigationProps>) { | |||
const branchLikes = mockSetOfBranchAndPullRequest(); | |||
return shallow( | |||
<BranchLikeNavigation | |||
appState={mockAppState()} | |||
branchLikes={branchLikes} | |||
component={mockComponent()} | |||
currentBranchLike={branchLikes[0]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,106 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockComponent, mockMainBranch } from '../../../../../../helpers/testMocks'; | |||
import { ComponentQualifier } from '../../../../../../types/component'; | |||
import { CurrentBranchLike, CurrentBranchLikeProps } from '../CurrentBranchLike'; | |||
describe('CurrentBranchLikeRenderer should render correctly for application when', () => { | |||
test('there is only one branch and the user can admin the application', () => { | |||
const wrapper = shallowRender({ | |||
component: mockComponent({ | |||
configuration: { showSettings: true }, | |||
qualifier: ComponentQualifier.Application | |||
}), | |||
hasManyBranches: false | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
test("there is only one branch and the user CAN'T admin the application", () => { | |||
const wrapper = shallowRender({ | |||
component: mockComponent({ | |||
configuration: { showSettings: false }, | |||
qualifier: ComponentQualifier.Application | |||
}), | |||
hasManyBranches: false | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
test('there are many branchlikes', () => { | |||
const wrapper = shallowRender({ | |||
branchesEnabled: true, | |||
component: mockComponent({ | |||
qualifier: ComponentQualifier.Application | |||
}), | |||
hasManyBranches: true | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
}); | |||
describe('CurrentBranchLikeRenderer should render correctly for project when', () => { | |||
test('branches support is disabled', () => { | |||
const wrapper = shallowRender({ | |||
branchesEnabled: false, | |||
component: mockComponent({ | |||
qualifier: ComponentQualifier.Project | |||
}) | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
test('there is only one branchlike', () => { | |||
const wrapper = shallowRender({ | |||
branchesEnabled: true, | |||
component: mockComponent({ | |||
qualifier: ComponentQualifier.Project | |||
}), | |||
hasManyBranches: false | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
test('there are many branchlikes', () => { | |||
const wrapper = shallowRender({ | |||
branchesEnabled: true, | |||
component: mockComponent({ | |||
qualifier: ComponentQualifier.Project | |||
}), | |||
hasManyBranches: true | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
}); | |||
function shallowRender(props?: Partial<CurrentBranchLikeProps>) { | |||
return shallow( | |||
<CurrentBranchLike | |||
branchesEnabled={false} | |||
component={mockComponent()} | |||
currentBranchLike={mockMainBranch()} | |||
hasManyBranches={false} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,43 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockMainBranch, mockPullRequest } from '../../../../../../helpers/testMocks'; | |||
import { | |||
CurrentBranchLikeMergeInformation, | |||
CurrentBranchLikeMergeInformationProps | |||
} from '../CurrentBranchLikeMergeInformation'; | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should not render for non-pull-request branch like', () => { | |||
const wrapper = shallowRender({ currentBranchLike: mockMainBranch() }); | |||
expect(wrapper.type()).toBeNull(); | |||
}); | |||
function shallowRender(props?: Partial<CurrentBranchLikeMergeInformationProps>) { | |||
return shallow( | |||
<CurrentBranchLikeMergeInformation currentBranchLike={mockPullRequest()} {...props} /> | |||
); | |||
} |
@@ -0,0 +1,122 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import SearchBox from 'sonar-ui-common/components/controls/SearchBox'; | |||
import { KeyCodes } from 'sonar-ui-common/helpers/keycodes'; | |||
import { click, mockEvent } from 'sonar-ui-common/helpers/testUtils'; | |||
import { mockSetOfBranchAndPullRequest } from '../../../../../../helpers/mocks/branch-pull-request'; | |||
import { mockComponent, mockPullRequest, mockRouter } from '../../../../../../helpers/testMocks'; | |||
import { Menu } from '../Menu'; | |||
import { MenuItemList } from '../MenuItemList'; | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should render correctly with no current branch like', () => { | |||
const wrapper = shallowRender({ currentBranchLike: undefined }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should close the menu when "manage branches" link is clicked', () => { | |||
const onClose = jest.fn(); | |||
const wrapper = shallowRender({ onClose }); | |||
click(wrapper.find(Link)); | |||
expect(onClose).toHaveBeenCalled(); | |||
}); | |||
it('should change url and close menu when an element is selected', () => { | |||
const onClose = jest.fn(); | |||
const push = jest.fn(); | |||
const router = mockRouter({ push }); | |||
const component = mockComponent(); | |||
const pr = mockPullRequest(); | |||
const wrapper = shallowRender({ component, onClose, router }); | |||
wrapper | |||
.find(MenuItemList) | |||
.props() | |||
.onSelect(pr); | |||
expect(onClose).toHaveBeenCalled(); | |||
expect(push).toHaveBeenCalledWith( | |||
expect.objectContaining({ | |||
query: { | |||
id: component.key, | |||
pullRequest: pr.key | |||
} | |||
}) | |||
); | |||
}); | |||
it('should filter branchlike list correctly', () => { | |||
const wrapper = shallowRender(); | |||
wrapper | |||
.find(SearchBox) | |||
.props() | |||
.onChange('PR'); | |||
expect(wrapper.state().branchLikesToDisplay.length).toBe(3); | |||
}); | |||
it('should handle keyboard shortcut correctly', () => { | |||
const push = jest.fn(); | |||
const router = mockRouter({ push }); | |||
const wrapper = shallowRender({ currentBranchLike: branchLikes[1], router }); | |||
const { onKeyDown } = wrapper.find(SearchBox).props(); | |||
if (!onKeyDown) { | |||
fail('onKeyDown should be defined'); | |||
} else { | |||
onKeyDown(mockEvent({ keyCode: KeyCodes.UpArrow })); | |||
expect(wrapper.state().selectedBranchLike).toBe(branchLikes[5]); | |||
onKeyDown(mockEvent({ keyCode: KeyCodes.DownArrow })); | |||
onKeyDown(mockEvent({ keyCode: KeyCodes.DownArrow })); | |||
expect(wrapper.state().selectedBranchLike).toBe(branchLikes[7]); | |||
onKeyDown(mockEvent({ keyCode: KeyCodes.Enter })); | |||
expect(push).toHaveBeenCalled(); | |||
} | |||
}); | |||
const branchLikes = mockSetOfBranchAndPullRequest(); | |||
function shallowRender(props?: Partial<Menu['props']>) { | |||
return shallow<Menu>( | |||
<Menu | |||
branchLikes={branchLikes} | |||
canAdminComponent={true} | |||
component={mockComponent()} | |||
currentBranchLike={branchLikes[2]} | |||
onClose={jest.fn()} | |||
router={mockRouter()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -17,42 +17,43 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import ComponentNavBranchesMenuItem, { Props } from '../ComponentNavBranchesMenuItem'; | |||
const component = { key: 'component' } as T.Component; | |||
const shortBranch: T.ShortLivingBranch = { | |||
isMain: false, | |||
mergeBranch: 'master', | |||
name: 'foo', | |||
status: { qualityGateStatus: 'ERROR' }, | |||
type: 'SHORT' | |||
}; | |||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||
import { | |||
mockComponent, | |||
mockMainBranch, | |||
mockPullRequest | |||
} from '../../../../../../helpers/testMocks'; | |||
import { MenuItem, MenuItemProps } from '../MenuItem'; | |||
const mainBranch: T.MainBranch = { isMain: true, name: 'master' }; | |||
it('renders main branch', () => { | |||
expect(shallowRender({ branchLike: mainBranch })).toMatchSnapshot(); | |||
it('should render a main branch correctly', () => { | |||
const wrapper = shallowRender({ branchLike: mockMainBranch() }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('renders short-living branch', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
it('should render a non-main branch, indented and selected item correctly', () => { | |||
const wrapper = shallowRender({ branchLike: mockPullRequest(), indent: true, selected: true }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('renders short-living orhpan branch', () => { | |||
const orhpan: T.ShortLivingBranch = { ...shortBranch, isOrphan: true }; | |||
expect(shallowRender({ branchLike: orhpan })).toMatchSnapshot(); | |||
it('should propagate click event correctly', () => { | |||
const onSelect = jest.fn(); | |||
const wrapper = shallowRender({ onSelect }); | |||
click(wrapper.find('li')); | |||
expect(onSelect).toHaveBeenCalled(); | |||
}); | |||
function shallowRender(props?: { [P in keyof Props]?: Props[P] }) { | |||
function shallowRender(props?: Partial<MenuItemProps>) { | |||
return shallow( | |||
<ComponentNavBranchesMenuItem | |||
branchLike={shortBranch} | |||
component={component} | |||
<MenuItem | |||
branchLike={mockMainBranch()} | |||
component={mockComponent()} | |||
onSelect={jest.fn()} | |||
selected={false} | |||
setSelectedNode={jest.fn()} | |||
{...props} | |||
/> | |||
); |
@@ -0,0 +1,50 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { getBrancheLikesAsTree } from '../../../../../../helpers/branches'; | |||
import { mockSetOfBranchAndPullRequest } from '../../../../../../helpers/mocks/branch-pull-request'; | |||
import { mockComponent, mockPullRequest } from '../../../../../../helpers/testMocks'; | |||
import { MenuItemList, MenuItemListProps } from '../MenuItemList'; | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props?: Partial<MenuItemListProps>) { | |||
const branchLikes = [ | |||
...mockSetOfBranchAndPullRequest(), | |||
mockPullRequest({ base: 'not-in-the-list' }) | |||
]; | |||
const branchLikeTree = getBrancheLikesAsTree(branchLikes); | |||
return shallow( | |||
<MenuItemList | |||
branchLikeTree={branchLikeTree} | |||
component={mockComponent()} | |||
hasResults={false} | |||
onSelect={jest.fn()} | |||
selectedBranchLike={branchLikes[0]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,201 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<span | |||
className="big-spacer-left flex-shrink branch-like-navigation-toggler-container" | |||
> | |||
<Memo(CurrentBranchLike) | |||
branchesEnabled={false} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
currentBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
} | |||
} | |||
hasManyBranches={true} | |||
/> | |||
</span> | |||
`; | |||
exports[`should render the menu trigger if branches are enabled 1`] = ` | |||
<span | |||
className="big-spacer-left flex-shrink branch-like-navigation-toggler-container dropdown" | |||
> | |||
<Toggler | |||
onRequestClose={[Function]} | |||
open={false} | |||
overlay={ | |||
<withRouter(Menu) | |||
branchLikes={ | |||
Array [ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-1", | |||
"type": "LONG", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1", | |||
"target": "master", | |||
"title": "PR-1", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "llb-1", | |||
"name": "slb-2", | |||
"type": "SHORT", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "2", | |||
"target": "master", | |||
"title": "PR-2", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-3", | |||
"type": "LONG", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-2", | |||
"type": "LONG", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"isOrphan": true, | |||
"key": "2", | |||
"target": "llb-100", | |||
"title": "PR-2", | |||
}, | |||
] | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
currentBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
} | |||
} | |||
onClose={[Function]} | |||
/> | |||
} | |||
> | |||
<a | |||
className="link-base-color link-no-underline" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<Memo(CurrentBranchLike) | |||
branchesEnabled={true} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
currentBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
} | |||
} | |||
hasManyBranches={true} | |||
/> | |||
</a> | |||
</Toggler> | |||
</span> | |||
`; |
@@ -0,0 +1,185 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`CurrentBranchLikeRenderer should render correctly for application when there are many branchlikes 1`] = ` | |||
<span | |||
className="display-flex-center flex-shrink text-ellipsis" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
/> | |||
<span | |||
className="spacer-left spacer-right flex-shrink text-ellipsis" | |||
title="master" | |||
> | |||
master | |||
</span> | |||
<DropdownIcon /> | |||
</span> | |||
`; | |||
exports[`CurrentBranchLikeRenderer should render correctly for application when there is only one branch and the user CAN'T admin the application 1`] = ` | |||
<span | |||
className="display-flex-center flex-shrink text-ellipsis" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
/> | |||
<span | |||
className="spacer-left spacer-right flex-shrink text-ellipsis" | |||
title="master" | |||
> | |||
master | |||
</span> | |||
</span> | |||
`; | |||
exports[`CurrentBranchLikeRenderer should render correctly for application when there is only one branch and the user can admin the application 1`] = ` | |||
<span | |||
className="display-flex-center flex-shrink text-ellipsis" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
/> | |||
<span | |||
className="spacer-left spacer-right flex-shrink text-ellipsis" | |||
title="master" | |||
> | |||
master | |||
</span> | |||
<HelpTooltip | |||
overlay={ | |||
<React.Fragment> | |||
<p> | |||
application.branches.help | |||
</p> | |||
<hr | |||
className="spacer-top spacer-bottom" | |||
/> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/admin/extension/governance/console", | |||
"query": Object { | |||
"id": "my-project", | |||
"qualifier": "APP", | |||
}, | |||
} | |||
} | |||
> | |||
application.branches.link | |||
</Link> | |||
</React.Fragment> | |||
} | |||
> | |||
<PlusCircleIcon | |||
fill="#4b9fd5" | |||
size={12} | |||
/> | |||
</HelpTooltip> | |||
</span> | |||
`; | |||
exports[`CurrentBranchLikeRenderer should render correctly for project when branches support is disabled 1`] = ` | |||
<span | |||
className="display-flex-center flex-shrink text-ellipsis" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
/> | |||
<span | |||
className="spacer-left spacer-right flex-shrink text-ellipsis" | |||
title="master" | |||
> | |||
master | |||
</span> | |||
<DocTooltip | |||
data-test="branches-support-disabled" | |||
doc={Promise {}} | |||
> | |||
<PlusCircleIcon | |||
fill="#4b9fd5" | |||
size={12} | |||
/> | |||
</DocTooltip> | |||
</span> | |||
`; | |||
exports[`CurrentBranchLikeRenderer should render correctly for project when there are many branchlikes 1`] = ` | |||
<span | |||
className="display-flex-center flex-shrink text-ellipsis" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
/> | |||
<span | |||
className="spacer-left spacer-right flex-shrink text-ellipsis" | |||
title="master" | |||
> | |||
master | |||
</span> | |||
<DropdownIcon /> | |||
</span> | |||
`; | |||
exports[`CurrentBranchLikeRenderer should render correctly for project when there is only one branchlike 1`] = ` | |||
<span | |||
className="display-flex-center flex-shrink text-ellipsis" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
/> | |||
<span | |||
className="spacer-left spacer-right flex-shrink text-ellipsis" | |||
title="master" | |||
> | |||
master | |||
</span> | |||
<DocTooltip | |||
data-test="only-one-branch-like" | |||
doc={Promise {}} | |||
> | |||
<PlusCircleIcon | |||
fill="#4b9fd5" | |||
size={12} | |||
/> | |||
</DocTooltip> | |||
</span> | |||
`; |
@@ -0,0 +1,22 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<span | |||
className="big-spacer-left flex-shrink note text-ellipsis" | |||
> | |||
<FormattedMessage | |||
defaultMessage="branch_like_navigation.for_merge_into_x_from_y" | |||
id="branch_like_navigation.for_merge_into_x_from_y" | |||
values={ | |||
Object { | |||
"branch": <strong> | |||
feature/foo/bar | |||
</strong>, | |||
"target": <strong> | |||
master | |||
</strong>, | |||
} | |||
} | |||
/> | |||
</span> | |||
`; |
@@ -0,0 +1,335 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<DropdownOverlay | |||
className="branch-like-navigation-menu" | |||
noPadding={true} | |||
> | |||
<div | |||
className="search-box-container" | |||
> | |||
<SearchBox | |||
autoFocus={true} | |||
onChange={[Function]} | |||
onKeyDown={[Function]} | |||
placeholder="branch_like_navigation.search_for_branch_like" | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="item-list-container" | |||
> | |||
<Memo(MenuItemList) | |||
branchLikeTree={ | |||
Object { | |||
"branchTree": Array [ | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-1", | |||
"type": "LONG", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-2", | |||
"type": "LONG", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-3", | |||
"type": "LONG", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "llb-1", | |||
"name": "slb-2", | |||
"type": "SHORT", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
], | |||
"mainBranchTree": Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
}, | |||
"pullRequests": Array [ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1", | |||
"target": "master", | |||
"title": "PR-1", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "2", | |||
"target": "master", | |||
"title": "PR-2", | |||
}, | |||
], | |||
}, | |||
"orphanPullRequests": Array [ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"isOrphan": true, | |||
"key": "2", | |||
"target": "llb-100", | |||
"title": "PR-2", | |||
}, | |||
], | |||
"parentlessPullRequests": Array [], | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
hasResults={true} | |||
onSelect={[Function]} | |||
selectedBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="hint-container text-right" | |||
> | |||
<Link | |||
onClick={[Function]} | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/branches", | |||
"query": Object { | |||
"id": "my-project", | |||
}, | |||
} | |||
} | |||
> | |||
branch_like_navigation.manage | |||
</Link> | |||
</div> | |||
</DropdownOverlay> | |||
`; | |||
exports[`should render correctly with no current branch like 1`] = ` | |||
<DropdownOverlay | |||
className="branch-like-navigation-menu" | |||
noPadding={true} | |||
> | |||
<div | |||
className="search-box-container" | |||
> | |||
<SearchBox | |||
autoFocus={true} | |||
onChange={[Function]} | |||
onKeyDown={[Function]} | |||
placeholder="branch_like_navigation.search_for_branch_like" | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="item-list-container" | |||
> | |||
<Memo(MenuItemList) | |||
branchLikeTree={ | |||
Object { | |||
"branchTree": Array [ | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-1", | |||
"type": "LONG", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-2", | |||
"type": "LONG", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-3", | |||
"type": "LONG", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "llb-1", | |||
"name": "slb-2", | |||
"type": "SHORT", | |||
}, | |||
"pullRequests": Array [], | |||
}, | |||
], | |||
"mainBranchTree": Object { | |||
"branch": Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
}, | |||
"pullRequests": Array [ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1", | |||
"target": "master", | |||
"title": "PR-1", | |||
}, | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "2", | |||
"target": "master", | |||
"title": "PR-2", | |||
}, | |||
], | |||
}, | |||
"orphanPullRequests": Array [ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"isOrphan": true, | |||
"key": "2", | |||
"target": "llb-100", | |||
"title": "PR-2", | |||
}, | |||
], | |||
"parentlessPullRequests": Array [], | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
hasResults={true} | |||
onSelect={[Function]} | |||
selectedBranchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="hint-container text-right" | |||
> | |||
<Link | |||
onClick={[Function]} | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/branches", | |||
"query": Object { | |||
"id": "my-project", | |||
}, | |||
} | |||
} | |||
> | |||
branch_like_navigation.manage | |||
</Link> | |||
</div> | |||
</DropdownOverlay> | |||
`; |
@@ -0,0 +1,102 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render a main branch correctly 1`] = ` | |||
<li | |||
className="item" | |||
onClick={[Function]} | |||
> | |||
<div | |||
className="display-flex-center display-flex-space-between" | |||
> | |||
<div | |||
className="item-name text-ellipsis" | |||
title="master" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
/> | |||
<span | |||
className="spacer-left" | |||
> | |||
master | |||
</span> | |||
<span | |||
className="badge spacer-left" | |||
> | |||
branches.main_branch | |||
</span> | |||
</div> | |||
<div | |||
className="spacer-left" | |||
> | |||
<Connect(BranchStatus) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component="my-project" | |||
/> | |||
</div> | |||
</div> | |||
</li> | |||
`; | |||
exports[`should render a non-main branch, indented and selected item correctly 1`] = ` | |||
<li | |||
className="item active" | |||
onClick={[Function]} | |||
> | |||
<div | |||
className="display-flex-center display-flex-space-between big-spacer-left" | |||
> | |||
<div | |||
className="item-name text-ellipsis" | |||
title="1001 – Foo Bar feature" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "master", | |||
"title": "Foo Bar feature", | |||
} | |||
} | |||
/> | |||
<span | |||
className="spacer-left" | |||
> | |||
1001 – Foo Bar feature | |||
</span> | |||
</div> | |||
<div | |||
className="spacer-left" | |||
> | |||
<Connect(BranchStatus) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "master", | |||
"title": "Foo Bar feature", | |||
} | |||
} | |||
component="my-project" | |||
/> | |||
</div> | |||
</div> | |||
</li> | |||
`; |
@@ -0,0 +1,428 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<ul | |||
className="item-list" | |||
> | |||
<li | |||
className="item" | |||
> | |||
<span | |||
className="note" | |||
> | |||
no_results | |||
</span> | |||
</li> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
key="branch-master" | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
setSelectedNode={[Function]} | |||
/> | |||
<li | |||
className="item header" | |||
> | |||
<span | |||
className="big-spacer-left" | |||
> | |||
branch_like_navigation.pull_requests | |||
</span> | |||
</li> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1", | |||
"target": "master", | |||
"title": "PR-1", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
indent={true} | |||
key="pull-request-1" | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
setSelectedNode={[Function]} | |||
/> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "2", | |||
"target": "master", | |||
"title": "PR-2", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
indent={true} | |||
key="pull-request-2" | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
setSelectedNode={[Function]} | |||
/> | |||
<hr /> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-1", | |||
"type": "LONG", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
key="branch-llb-1" | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
setSelectedNode={[Function]} | |||
/> | |||
<hr /> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-2", | |||
"type": "LONG", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
key="branch-llb-2" | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
setSelectedNode={[Function]} | |||
/> | |||
<hr /> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"name": "llb-3", | |||
"type": "LONG", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
key="branch-llb-3" | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
setSelectedNode={[Function]} | |||
/> | |||
<hr /> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
"name": "slb-1", | |||
"type": "SHORT", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
key="branch-slb-1" | |||
onSelect={[MockFunction]} | |||
selected={true} | |||
setSelectedNode={[Function]} | |||
/> | |||
<hr /> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"isMain": false, | |||
"mergeBranch": "llb-1", | |||
"name": "slb-2", | |||
"type": "SHORT", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
key="branch-slb-2" | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
setSelectedNode={[Function]} | |||
/> | |||
<hr /> | |||
<li | |||
className="item header" | |||
> | |||
branch_like_navigation.pull_requests | |||
</li> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "not-in-the-list", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "master", | |||
"title": "Foo Bar feature", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
key="pull-request-1001" | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
setSelectedNode={[Function]} | |||
/> | |||
<li | |||
className="item header" | |||
> | |||
branch_like_navigation.orphan_pull_requests | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="branch_like_navigation.orphan_pull_requests.tooltip" | |||
/> | |||
</li> | |||
<Memo(MenuItem) | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"isOrphan": true, | |||
"key": "2", | |||
"target": "llb-100", | |||
"title": "PR-2", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"organization": "foo", | |||
"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 [], | |||
} | |||
} | |||
key="pull-request-2" | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
setSelectedNode={[Function]} | |||
/> | |||
</ul> | |||
`; |
@@ -29,7 +29,7 @@ exports[`should render correctly 1`] = ` | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchIcon | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
@@ -79,7 +79,7 @@ exports[`should render correctly 1`] = ` | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchIcon | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
@@ -115,7 +115,7 @@ exports[`should render correctly 1`] = ` | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchIcon | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", |
@@ -24,7 +24,7 @@ import ActionsDropdown, { | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { listBranchesNewCodePeriod, resetNewCodePeriod } from '../../../api/newCodePeriod'; | |||
import BranchIcon from '../../../components/icons-components/BranchIcon'; | |||
import BranchLikeIcon from '../../../components/icons/BranchLikeIcon'; | |||
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; | |||
import { isBranch, sortBranches } from '../../../helpers/branches'; | |||
import BranchBaselineSettingModal from './BranchBaselineSettingModal'; | |||
@@ -176,7 +176,7 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
{branches.map(branch => ( | |||
<tr key={branch.name}> | |||
<td className="nowrap"> | |||
<BranchIcon branchLike={branch} className="little-spacer-right" /> | |||
<BranchLikeIcon branchLike={branch} className="little-spacer-right" /> | |||
{branch.name} | |||
{branch.isMain && ( | |||
<div className="badge spacer-left">{translate('branches.main_branch')}</div> |
@@ -24,7 +24,7 @@ import ActionsDropdown, { | |||
} from 'sonar-ui-common/components/controls/ActionsDropdown'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import BranchStatus from '../../../components/common/BranchStatus'; | |||
import BranchIcon from '../../../components/icons-components/BranchIcon'; | |||
import BranchLikeIcon from '../../../components/icons/BranchLikeIcon'; | |||
import DateFromNow from '../../../components/intl/DateFromNow'; | |||
import { getBranchLikeDisplayName, isMainBranch, isPullRequest } from '../../../helpers/branches'; | |||
@@ -41,7 +41,7 @@ export function BranchLikeRowRenderer(props: BranchLikeRowRendererProps) { | |||
return ( | |||
<tr> | |||
<td> | |||
<BranchIcon branchLike={branchLike} className="little-spacer-right" /> | |||
<BranchLikeIcon branchLike={branchLike} className="little-spacer-right" /> | |||
{getBranchLikeDisplayName(branchLike)} | |||
{isMainBranch(branchLike) && ( | |||
<div className="badge spacer-left">{translate('branches.main_branch')}</div> |
@@ -42,7 +42,7 @@ export function BranchLikeTableRenderer(props: BranchLikeTableRendererProps) { | |||
<th>{tableTitle}</th> | |||
<th className="thin nowrap">{translate('status')}</th> | |||
<th className="thin nowrap text-right big-spacer-left"> | |||
{translate('branches.last_analysis_date')} | |||
{translate('project_branch_pull_request.last_analysis_date')} | |||
</th> | |||
<th className="thin nowrap text-right">{translate('actions')}</th> | |||
</tr> |
@@ -3,7 +3,7 @@ | |||
exports[`should render correctly for long lived branch 1`] = ` | |||
<tr> | |||
<td> | |||
<BranchIcon | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
@@ -59,7 +59,7 @@ exports[`should render correctly for long lived branch 1`] = ` | |||
exports[`should render correctly for mai branch 1`] = ` | |||
<tr> | |||
<td> | |||
<BranchIcon | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
@@ -117,7 +117,7 @@ exports[`should render correctly for mai branch 1`] = ` | |||
exports[`should render correctly for pull request 1`] = ` | |||
<tr> | |||
<td> | |||
<BranchIcon | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
@@ -177,7 +177,7 @@ exports[`should render correctly for pull request 1`] = ` | |||
exports[`should render correctly for short lived branch 1`] = ` | |||
<tr> | |||
<td> | |||
<BranchIcon | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", |
@@ -20,7 +20,7 @@ exports[`should render correctly 1`] = ` | |||
<th | |||
className="thin nowrap text-right big-spacer-left" | |||
> | |||
branches.last_analysis_date | |||
project_branch_pull_request.last_analysis_date | |||
</th> | |||
<th | |||
className="thin nowrap text-right" |
@@ -23,7 +23,7 @@ import { getAppState, Store } from '../../store/rootReducer'; | |||
import { getWrappedDisplayName } from './utils'; | |||
export function withAppState<P>( | |||
WrappedComponent: React.ComponentClass<P & { appState: Partial<T.AppState> }> | |||
WrappedComponent: React.ComponentType<P & { appState: Partial<T.AppState> }> | |||
) { | |||
class Wrapper extends React.Component<P & { appState: T.AppState }> { | |||
static displayName = getWrappedDisplayName(WrappedComponent, 'withAppState'); |
@@ -23,11 +23,11 @@ import PullRequestIcon from 'sonar-ui-common/components/icons/PullRequestIcon'; | |||
import ShortLivingBranchIcon from 'sonar-ui-common/components/icons/ShortLivingBranchIcon'; | |||
import { isPullRequest } from '../../helpers/branches'; | |||
export interface BranchIconProps extends IconProps { | |||
export interface BranchLikeIconProps extends IconProps { | |||
branchLike: T.BranchLike; | |||
} | |||
export default function BranchIcon({ branchLike, ...props }: BranchIconProps) { | |||
export default function BranchLikeIcon({ branchLike, ...props }: BranchLikeIconProps) { | |||
if (isPullRequest(branchLike)) { | |||
return <PullRequestIcon {...props} />; | |||
} else { |
@@ -25,7 +25,7 @@ import { | |||
mockPullRequest, | |||
mockShortLivingBranch | |||
} from '../../../helpers/testMocks'; | |||
import BranchIcon, { BranchIconProps } from '../BranchIcon'; | |||
import BranchLikeIcon, { BranchLikeIconProps } from '../BranchLikeIcon'; | |||
it('should render short living branch icon for short living branch', () => { | |||
const wrapper = shallowRender({ branchLike: mockShortLivingBranch() }); | |||
@@ -42,6 +42,6 @@ it('should render pull request icon correctly', () => { | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: BranchIconProps) { | |||
return shallow(<BranchIcon {...props} />); | |||
function shallowRender(props: BranchLikeIconProps) { | |||
return shallow(<BranchLikeIcon {...props} />); | |||
} |
@@ -17,7 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { isSameBranchLike, sortBranches, sortBranchesAsTree } from '../branches'; | |||
import { getBrancheLikesAsTree, isSameBranchLike, sortBranches } from '../branches'; | |||
import { | |||
mockLongLivingBranch, | |||
mockMainBranch, | |||
@@ -25,40 +26,57 @@ import { | |||
mockShortLivingBranch | |||
} from '../testMocks'; | |||
describe('#sortBranchesAsTree', () => { | |||
it('sorts main branch and short-living branches', () => { | |||
const main = mockMainBranch(); | |||
const foo = mockShortLivingBranch({ name: 'foo' }); | |||
const bar = mockShortLivingBranch({ name: 'bar' }); | |||
expect(sortBranchesAsTree([main, foo, bar])).toEqual([main, bar, foo]); | |||
}); | |||
describe('#getBrancheLikesAsTree', () => { | |||
it('should correctly map branches and prs to tree object', () => { | |||
const main = mockMainBranch({ name: 'master' }); | |||
const llb1 = mockLongLivingBranch({ name: 'llb1' }); | |||
const llb2 = mockLongLivingBranch({ name: 'llb2' }); | |||
const slb1 = mockShortLivingBranch({ name: 'slb1' }); | |||
const slb2 = mockShortLivingBranch({ name: 'slb2' }); | |||
it('sorts main branch and long-living branches', () => { | |||
const main = mockMainBranch(); | |||
const foo = mockLongLivingBranch({ name: 'foo' }); | |||
const bar = mockLongLivingBranch({ name: 'bar' }); | |||
expect(sortBranchesAsTree([main, foo, bar])).toEqual([main, bar, foo]); | |||
}); | |||
const mainPr1 = mockPullRequest({ base: main.name, key: 'PR1' }); | |||
const mainPr2 = mockPullRequest({ base: main.name, key: 'PR2' }); | |||
const llb1Pr1 = mockPullRequest({ base: llb1.name, key: 'PR1' }); | |||
const llb1Pr2 = mockPullRequest({ base: llb1.name, key: 'PR2' }); | |||
const llb2Pr1 = mockPullRequest({ base: llb2.name, key: 'PR1' }); | |||
const llb2Pr2 = mockPullRequest({ base: llb2.name, key: 'PR1' }); | |||
const orphanPR1 = mockPullRequest({ isOrphan: true, key: 'PR1' }); | |||
const orphanPR2 = mockPullRequest({ isOrphan: true, key: 'PR2' }); | |||
const parentlessPR1 = mockPullRequest({ base: 'not_present_branch_1', key: 'PR1' }); | |||
const parentlessPR2 = mockPullRequest({ base: 'not_present_branch_2', key: 'PR2' }); | |||
it('sorts all types of branches', () => { | |||
const main = mockMainBranch(); | |||
const shortFoo = mockShortLivingBranch({ name: 'shortFoo', mergeBranch: 'master' }); | |||
const shortBar = mockShortLivingBranch({ name: 'shortBar', mergeBranch: 'longBaz' }); | |||
const shortPre = mockShortLivingBranch({ name: 'shortPre', mergeBranch: 'shortFoo' }); | |||
const longBaz = mockLongLivingBranch({ name: 'longBaz' }); | |||
const longQux = mockLongLivingBranch({ name: 'longQux' }); | |||
const longQwe = mockLongLivingBranch({ name: 'longQwe' }); | |||
const pr = mockPullRequest({ base: 'master' }); | |||
// - main - main | |||
// - shortFoo - shortFoo | |||
// - shortPre - shortPre | |||
// - longBaz ----> - longBaz | |||
// - shortBar - shortBar | |||
// - longQwe - longQwe | |||
// - longQux - longQux | |||
expect( | |||
sortBranchesAsTree([main, shortFoo, shortBar, shortPre, longBaz, longQux, longQwe, pr]) | |||
).toEqual([main, pr, shortFoo, shortPre, longBaz, shortBar, longQux, longQwe]); | |||
getBrancheLikesAsTree([ | |||
llb2, | |||
llb1, | |||
main, | |||
orphanPR2, | |||
orphanPR1, | |||
slb2, | |||
slb1, | |||
mainPr2, | |||
mainPr1, | |||
parentlessPR2, | |||
parentlessPR1, | |||
llb2Pr2, | |||
llb2Pr1, | |||
llb1Pr2, | |||
llb1Pr1 | |||
]) | |||
).toEqual({ | |||
mainBranchTree: { | |||
branch: main, | |||
pullRequests: [mainPr1, mainPr2] | |||
}, | |||
branchTree: [ | |||
{ branch: llb1, pullRequests: [llb1Pr1, llb1Pr2] }, | |||
{ branch: llb2, pullRequests: [llb2Pr1, llb2Pr1] }, | |||
{ branch: slb1, pullRequests: [] }, | |||
{ branch: slb2, pullRequests: [] } | |||
], | |||
parentlessPullRequests: [parentlessPR1, parentlessPR2], | |||
orphanPullRequests: [orphanPR1, orphanPR2] | |||
}); | |||
}); | |||
}); | |||
@@ -17,7 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { orderBy, sortBy } from 'lodash'; | |||
import { orderBy } from 'lodash'; | |||
export function isBranch(branchLike?: T.BranchLike): branchLike is T.Branch { | |||
return branchLike !== undefined && (branchLike as T.Branch).isMain !== undefined; | |||
@@ -96,58 +97,32 @@ export function isSameBranchLike(a: T.BranchLike | undefined, b: T.BranchLike | | |||
return a === b; | |||
} | |||
export function sortBranchesAsTree(branchLikes: T.BranchLike[]) { | |||
const result: T.BranchLike[] = []; | |||
export function getBrancheLikesAsTree(branchLikes: T.BranchLike[]): T.BranchLikeTree { | |||
const mainBranch = branchLikes.find(isMainBranch); | |||
const longLivingBranches = branchLikes.filter(isLongLivingBranch); | |||
const shortLivingBranches = branchLikes.filter(isShortLivingBranch); | |||
const pullRequests = branchLikes.filter(isPullRequest); | |||
// main branch is always first | |||
if (mainBranch) { | |||
result.push( | |||
mainBranch, | |||
...getPullRequests(mainBranch.name), | |||
...getNestedShortLivingBranches(mainBranch.name) | |||
); | |||
} | |||
// then all long-living branches | |||
sortBy(longLivingBranches, 'name').forEach(longLivingBranch => { | |||
result.push( | |||
longLivingBranch, | |||
...getPullRequests(longLivingBranch.name), | |||
...getNestedShortLivingBranches(longLivingBranch.name) | |||
); | |||
}); | |||
// finally all orhpan pull requests and branches | |||
result.push( | |||
...sortBy(pullRequests.filter(pr => pr.isOrphan), pullRequest => pullRequest.key), | |||
...sortBy(shortLivingBranches.filter(branch => branch.isOrphan), branch => branch.name) | |||
const branches = orderBy(branchLikes.filter(isBranch).filter(b => !isMainBranch(b)), b => b.name); | |||
const pullRequests = orderBy(branchLikes.filter(isPullRequest), b => b.key); | |||
const parentlessPullRequests = pullRequests.filter( | |||
pr => !pr.isOrphan && ![mainBranch, ...branches].find(b => !!b && b.name === pr.base) | |||
); | |||
const orphanPullRequests = pullRequests.filter(pr => pr.isOrphan); | |||
return result; | |||
/** Get all short-living branches (possibly nested) which should be merged to a given branch */ | |||
function getNestedShortLivingBranches(mergeBranch: string) { | |||
const found: T.ShortLivingBranch[] = shortLivingBranches.filter( | |||
branch => branch.mergeBranch === mergeBranch | |||
); | |||
let i = 0; | |||
while (i < found.length) { | |||
const current = found[i]; | |||
found.push(...shortLivingBranches.filter(branch => branch.mergeBranch === current.name)); | |||
i++; | |||
} | |||
const tree: T.BranchLikeTree = { | |||
branchTree: branches.map(b => ({ branch: b, pullRequests: getPullRequests(b) })), | |||
parentlessPullRequests, | |||
orphanPullRequests | |||
}; | |||
return sortBy(found, branch => branch.name); | |||
if (mainBranch) { | |||
tree.mainBranchTree = { | |||
branch: mainBranch, | |||
pullRequests: getPullRequests(mainBranch) | |||
}; | |||
} | |||
function getPullRequests(base: string) { | |||
return sortBy(pullRequests.filter(pr => pr.base === base), pullRequest => pullRequest.key); | |||
return tree; | |||
function getPullRequests(branch: T.Branch) { | |||
return pullRequests.filter(pr => !pr.isOrphan && pr.base === branch.name); | |||
} | |||
} | |||
@@ -19,12 +19,7 @@ | |||
*/ | |||
import { getBaseUrl, Location } from 'sonar-ui-common/helpers/urls'; | |||
import { getProfilePath } from '../apps/quality-profiles/utils'; | |||
import { | |||
getBranchLikeQuery, | |||
isLongLivingBranch, | |||
isPullRequest, | |||
isShortLivingBranch | |||
} from './branches'; | |||
import { getBranchLikeQuery, isBranch, isMainBranch, isPullRequest } from './branches'; | |||
type Query = Location['query']; | |||
@@ -47,10 +42,8 @@ export function getComponentBackgroundTaskUrl(componentKey: string, status?: str | |||
export function getBranchLikeUrl(project: string, branchLike?: T.BranchLike): Location { | |||
if (isPullRequest(branchLike)) { | |||
return getPullRequestUrl(project, branchLike.key); | |||
} else if (isShortLivingBranch(branchLike)) { | |||
} else if (isBranch(branchLike) && !isMainBranch(branchLike)) { | |||
return getShortLivingBranchUrl(project, branchLike.name); | |||
} else if (isLongLivingBranch(branchLike)) { | |||
return getLongLivingBranchUrl(project, branchLike.name); | |||
} else { | |||
return getProjectUrl(project); | |||
} |
@@ -0,0 +1,78 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
declare namespace T { | |||
export type BranchType = 'LONG' | 'SHORT'; | |||
export interface Branch { | |||
analysisDate?: string; | |||
isMain: boolean; | |||
name: string; | |||
status?: { qualityGateStatus: Status }; | |||
} | |||
export interface MainBranch extends Branch { | |||
isMain: true; | |||
} | |||
export interface LongLivingBranch extends Branch { | |||
isMain: false; | |||
type: 'LONG'; | |||
} | |||
export interface ShortLivingBranch extends Branch { | |||
isMain: false; | |||
isOrphan?: true; | |||
mergeBranch: string; | |||
type: 'SHORT'; | |||
} | |||
export interface PullRequest { | |||
analysisDate?: string; | |||
base: string; | |||
branch: string; | |||
key: string; | |||
isOrphan?: true; | |||
status?: { qualityGateStatus: Status }; | |||
target: string; | |||
title: string; | |||
url?: string; | |||
} | |||
export type BranchLike = Branch | PullRequest; | |||
export interface BranchTree { | |||
branch: Branch; | |||
pullRequests: PullRequest[]; | |||
} | |||
export interface BranchLikeTree { | |||
mainBranchTree?: BranchTree; | |||
branchTree: BranchTree[]; | |||
parentlessPullRequests: PullRequest[]; | |||
orphanPullRequests: PullRequest[]; | |||
} | |||
export type BranchParameters = { branch?: string } | { pullRequest?: string }; | |||
export interface BranchWithNewCodePeriod extends Branch { | |||
newCodePeriod?: NewCodePeriod; | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
export enum ComponentQualifier { | |||
Application = 'APP', | |||
Directory = 'DIR', | |||
Developper = 'DEV', | |||
File = 'FIL', | |||
Portfolio = 'VW', | |||
Project = 'TRK', | |||
SubPortfolio = 'SVW', | |||
SubProject = 'BRC', | |||
TestFile = 'UTS' | |||
} |
@@ -108,23 +108,6 @@ declare namespace T { | |||
webAnalyticsJsPath?: string; | |||
} | |||
export interface Branch { | |||
analysisDate?: string; | |||
isMain: boolean; | |||
name: string; | |||
status?: { qualityGateStatus: Status }; | |||
} | |||
export type BranchLike = Branch | PullRequest; | |||
export type BranchParameters = { branch?: string } | { pullRequest?: string }; | |||
export type BranchType = 'LONG' | 'SHORT'; | |||
export interface BranchWithNewCodePeriod extends Branch { | |||
newCodePeriod?: NewCodePeriod; | |||
} | |||
export interface Breadcrumb { | |||
key: string; | |||
name: string; | |||
@@ -455,15 +438,6 @@ declare namespace T { | |||
settings?: CurrentUserSetting[]; | |||
} | |||
export interface LongLivingBranch extends Branch { | |||
isMain: false; | |||
type: 'LONG'; | |||
} | |||
export interface MainBranch extends Branch { | |||
isMain: true; | |||
} | |||
export interface Measure extends MeasureIntern { | |||
metric: string; | |||
} | |||
@@ -669,18 +643,6 @@ declare namespace T { | |||
url: string; | |||
} | |||
export interface PullRequest { | |||
analysisDate?: string; | |||
base: string; | |||
branch: string; | |||
key: string; | |||
isOrphan?: true; | |||
status?: { qualityGateStatus: Status }; | |||
target: string; | |||
title: string; | |||
url?: string; | |||
} | |||
export interface QualityGate { | |||
actions?: { | |||
associateProjects?: boolean; | |||
@@ -834,13 +796,6 @@ declare namespace T { | |||
values?: string[]; | |||
} | |||
export interface ShortLivingBranch extends Branch { | |||
isMain: false; | |||
isOrphan?: true; | |||
mergeBranch: string; | |||
type: 'SHORT'; | |||
} | |||
export interface Snippet { | |||
start: number; | |||
end: number; |
@@ -546,6 +546,8 @@ project_branch_pull_request.tabs.branches=Branches | |||
project_branch_pull_request.tabs.pull_requests=Pull Requests | |||
project_branch_pull_request.table.branch=Branch | |||
project_branch_pull_request.table.pull_request=Pull Request | |||
project_branch_pull_request.last_analysis_date=Last Analysis Date | |||
project_baseline.page=New Code Period | |||
project_baseline.page.description=Use this page to manage the New Code Period of your project. {link} | |||
project_baseline.page.description.link=Learn More | |||
@@ -3188,25 +3190,20 @@ onboarding.tutorial.return_to_tutorial=Return to tutorial | |||
# BRANCHES | |||
# | |||
#------------------------------------------------------------------------------ | |||
branches.manage=Manage branches | |||
branches.orphan_branch=Orphan Branch | |||
branches.orphan_branches=Orphan Branches & Pull Requests | |||
branches.orphan_branches.tooltip=When a target branch of a short-living branch or a base of a pull request was deleted, this short-living branch or pull request becomes orphan. | |||
branches.main_branch=Main Branch | |||
branches.branch_settings=Branch Settings | |||
branches.set_new_code_period=Set New Code Period | |||
branches.last_analysis_date=Last Analysis Date | |||
branches.search_for_branches=Search for branches... | |||
branches.pull_requests=Pull Requests | |||
branches.short_lived.quality_gate.description=The branch status is passed because there are no open issue. The remaining {0} issue(s) have been confirmed. | |||
branches.short_lived_branches=Short-lived branches | |||
branches.pull_request.for_merge_into_x_from_y=for merge into {target} from {branch} | |||
branches.see_the_pr=See the PR | |||
branches.measures.new_coverage.help=Coverage on New Code. See {link} for details. | |||
branches.measures.new_coverage.missing=No coverage data for new code. | |||
branches.measures.new_duplicated_lines_density.help=Duplications on New Code. See {link} for details. | |||
branches.measures.new_duplicated_lines_density.missing=No duplications data for new code. | |||
#------------------------------------------------------------------------------ | |||
# | |||
# BRANCH-LIKE NAVIGATION | |||
# | |||
#------------------------------------------------------------------------------ | |||
branch_like_navigation.manage=Manage branches and Pull Requests | |||
branch_like_navigation.search_for_branch_like=Search for branches or Pull Requests... | |||
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} | |||
#------------------------------------------------------------------------------ | |||
# |