@@ -26,6 +26,12 @@ export interface ApplicationLeak { | |||
projectName: string; | |||
} | |||
export function getApplicationLeak(application: string): Promise<Array<ApplicationLeak>> { | |||
return getJSON('/api/applications/show_leak', { application }).then(r => r.leaks, throwGlobalError); | |||
export function getApplicationLeak( | |||
application: string, | |||
branch?: string | |||
): Promise<Array<ApplicationLeak>> { | |||
return getJSON('/api/applications/show_leak', { application, branch }).then( | |||
r => r.leaks, | |||
throwGlobalError | |||
); | |||
} |
@@ -160,6 +160,7 @@ export interface ApplicationQualityGate { | |||
export function getApplicationQualityGate(data: { | |||
application: string; | |||
branch?: string; | |||
organization?: string; | |||
}): Promise<ApplicationQualityGate> { | |||
return getJSON('/api/qualitygates/application_status', data).catch(throwGlobalError); |
@@ -20,6 +20,7 @@ | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { Link } from 'react-router'; | |||
import ComponentNavBranchesMenu from './ComponentNavBranchesMenu'; | |||
import DocTooltip from '../../../../components/docs/DocTooltip'; | |||
import { BranchLike, Component } from '../../../types'; | |||
@@ -53,7 +54,8 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State | |||
mounted = false; | |||
static contextTypes = { | |||
branchesEnabled: PropTypes.bool.isRequired | |||
branchesEnabled: PropTypes.bool.isRequired, | |||
canAdmin: PropTypes.bool.isRequired | |||
}; | |||
state: State = { | |||
@@ -125,17 +127,34 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State | |||
} | |||
}; | |||
renderOverlay = () => { | |||
const adminLink = { | |||
pathname: '/project/admin/extension/governance/console', | |||
query: { id: this.props.component.breadcrumbs[0].key, qualifier: 'APP' } | |||
}; | |||
return ( | |||
<> | |||
<p>{translate('application.branches.help')}</p> | |||
<hr className="spacer-top spacer-bottom" /> | |||
<Link className="spacer-left link-no-underline" to={adminLink}> | |||
{translate('application.branches.link')} | |||
</Link> | |||
</> | |||
); | |||
}; | |||
render() { | |||
const { branchLikes, currentBranchLike } = this.props; | |||
const { configuration } = this.props.component; | |||
const { configuration, breadcrumbs } = this.props.component; | |||
if (isSonarCloud() && !this.context.branchesEnabled) { | |||
return null; | |||
} | |||
const displayName = getBranchLikeDisplayName(currentBranchLike); | |||
const isApp = breadcrumbs && breadcrumbs[0] && breadcrumbs[0].qualifier === 'APP'; | |||
if (!this.context.branchesEnabled) { | |||
if (isApp && branchLikes.length < 2) { | |||
return ( | |||
<div className="navbar-context-branches"> | |||
<BranchIcon | |||
@@ -144,23 +163,42 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State | |||
fill={theme.gray80} | |||
/> | |||
<span className="note">{displayName}</span> | |||
<DocTooltip className="spacer-left" doc="branches/no-branch-support"> | |||
<PlusCircleIcon fill={theme.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="branches/single-branch"> | |||
<PlusCircleIcon fill={theme.blue} size={12} /> | |||
</DocTooltip> | |||
{configuration && | |||
configuration.showSettings && ( | |||
<HelpTooltip className="spacer-left" overlay={this.renderOverlay()}> | |||
<PlusCircleIcon className="vertical-middle" fill={theme.blue} size={12} /> | |||
</HelpTooltip> | |||
)} | |||
</div> | |||
); | |||
} else { | |||
if (!this.context.branchesEnabled) { | |||
return ( | |||
<div className="navbar-context-branches"> | |||
<BranchIcon | |||
branchLike={currentBranchLike} | |||
className="little-spacer-right" | |||
fill={theme.gray80} | |||
/> | |||
<span className="note">{displayName}</span> | |||
<DocTooltip className="spacer-left" doc="branches/no-branch-support"> | |||
<PlusCircleIcon fill={theme.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="branches/single-branch"> | |||
<PlusCircleIcon fill={theme.blue} size={12} /> | |||
</DocTooltip> | |||
</div> | |||
); | |||
} | |||
} | |||
return ( |
@@ -400,9 +400,10 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
renderExtension = ({ key, name }: Extension, isAdmin: boolean) => { | |||
const pathname = isAdmin ? `/project/admin/extension/${key}` : `/project/extension/${key}`; | |||
const query = { id: this.props.component.key, qualifier: this.props.component.qualifier }; | |||
return ( | |||
<li key={key}> | |||
<Link to={{ pathname, query: { id: this.props.component.key } }} activeClassName="active"> | |||
<Link activeClassName="active" to={{ pathname, query }}> | |||
{name} | |||
</Link> | |||
</li> |
@@ -111,7 +111,8 @@ function getCurrentPage(component: Component, branchLike: BranchLike | undefined | |||
if (component.qualifier === 'VW' || component.qualifier === 'SVW') { | |||
currentPage = { type: HomePageType.Portfolio, component: component.key }; | |||
} else if (component.qualifier === 'APP') { | |||
currentPage = { type: HomePageType.Application, component: component.key }; | |||
const branch = isLongLivingBranch(branchLike) ? branchLike.name : undefined; | |||
currentPage = { type: HomePageType.Application, component: component.key, branch }; | |||
} else if (component.qualifier === 'TRK') { | |||
// when home page is set to the default branch of a project, its name is returned as `undefined` | |||
const branch = isLongLivingBranch(branchLike) ? branchLike.name : undefined; |
@@ -49,7 +49,7 @@ it('renders main branch', () => { | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
{ context: { branchesEnabled: true, canAdmin: true } } | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
@@ -70,7 +70,7 @@ it('renders short-living branch', () => { | |||
component={component} | |||
currentBranchLike={branch} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
{ context: { branchesEnabled: true, canAdmin: true } } | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
@@ -91,7 +91,7 @@ it('renders pull request', () => { | |||
component={component} | |||
currentBranchLike={pullRequest} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
{ context: { branchesEnabled: true, canAdmin: true } } | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
@@ -104,7 +104,7 @@ it('opens menu', () => { | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
{ context: { branchesEnabled: true, canAdmin: true } } | |||
); | |||
expect(wrapper.find('Toggler').prop('open')).toBe(false); | |||
click(wrapper.find('a')); | |||
@@ -119,7 +119,7 @@ it('renders single branch popup', () => { | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
{ context: { branchesEnabled: true, canAdmin: true } } | |||
); | |||
expect(wrapper.find('DocTooltip')).toMatchSnapshot(); | |||
}); | |||
@@ -132,7 +132,7 @@ it('renders no branch support popup', () => { | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: false } } | |||
{ context: { branchesEnabled: false, canAdmin: true } } | |||
); | |||
expect(wrapper.find('DocTooltip')).toMatchSnapshot(); | |||
}); | |||
@@ -146,7 +146,7 @@ it('renders nothing on SonarCloud without branch support', () => { | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: false, onSonarCloud: true } } | |||
{ context: { branchesEnabled: false, onSonarCloud: true, canAdmin: true } } | |||
); | |||
expect(wrapper.type()).toBeNull(); | |||
}); |
@@ -13,6 +13,7 @@ exports[`renders main branch 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "component", | |||
}, | |||
} |
@@ -23,6 +23,7 @@ exports[`should not render breadcrumbs with one element 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "my-project", | |||
}, | |||
} | |||
@@ -90,6 +91,7 @@ exports[`should render organization 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "my-project", | |||
}, | |||
} |
@@ -873,6 +873,7 @@ exports[`should work with extensions 1`] = ` | |||
"pathname": "/project/extension/component-foo", | |||
"query": Object { | |||
"id": "foo", | |||
"qualifier": "TRK", | |||
}, | |||
} | |||
} | |||
@@ -954,6 +955,7 @@ exports[`should work with extensions 2`] = ` | |||
"pathname": "/project/admin/extension/foo", | |||
"query": Object { | |||
"id": "foo", | |||
"qualifier": "TRK", | |||
}, | |||
} | |||
} | |||
@@ -1001,6 +1003,7 @@ exports[`should work with multiple extensions 1`] = ` | |||
"pathname": "/project/extension/component-foo", | |||
"query": Object { | |||
"id": "foo", | |||
"qualifier": "TRK", | |||
}, | |||
} | |||
} | |||
@@ -1018,6 +1021,7 @@ exports[`should work with multiple extensions 1`] = ` | |||
"pathname": "/project/extension/component-bar", | |||
"query": Object { | |||
"id": "foo", | |||
"qualifier": "TRK", | |||
}, | |||
} | |||
} | |||
@@ -1099,6 +1103,7 @@ exports[`should work with multiple extensions 2`] = ` | |||
"pathname": "/project/admin/extension/foo", | |||
"query": Object { | |||
"id": "foo", | |||
"qualifier": "TRK", | |||
}, | |||
} | |||
} | |||
@@ -1116,6 +1121,7 @@ exports[`should work with multiple extensions 2`] = ` | |||
"pathname": "/project/admin/extension/bar", | |||
"query": Object { | |||
"id": "foo", | |||
"qualifier": "TRK", | |||
}, | |||
} | |||
} |
@@ -21,6 +21,7 @@ exports[`renders favorite 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -69,6 +70,7 @@ exports[`renders match 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -116,6 +118,7 @@ exports[`renders organizations 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -168,6 +171,7 @@ exports[`renders organizations 2`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -215,6 +219,7 @@ exports[`renders projects 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "qwe", | |||
}, | |||
} | |||
@@ -267,6 +272,7 @@ exports[`renders recently browsed 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -314,6 +320,7 @@ exports[`renders selected 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -359,6 +366,7 @@ exports[`renders selected 2`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} |
@@ -169,7 +169,7 @@ export interface Group { | |||
} | |||
export type HomePage = | |||
| { type: HomePageType.Application; component: string } | |||
| { type: HomePageType.Application; branch: string | undefined; component: string } | |||
| { type: HomePageType.Issues } | |||
| { type: HomePageType.MyIssues } | |||
| { type: HomePageType.MyProjects } | |||
@@ -220,6 +220,7 @@ export interface Issue { | |||
assigneeLogin?: string; | |||
assigneeName?: string; | |||
author?: string; | |||
branch?: string; | |||
comments?: IssueComment[]; | |||
component: string; | |||
componentLongName: string; | |||
@@ -237,6 +238,7 @@ export interface Issue { | |||
projectName: string; | |||
projectOrganization: string; | |||
projectUuid: string; | |||
pullRequest?: string; | |||
resolution?: string; | |||
rule: string; | |||
ruleName: string; |
@@ -25,6 +25,7 @@ exports[`should match snapshot 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} |
@@ -20,6 +20,7 @@ exports[`renders correctly 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} |
@@ -42,5 +42,5 @@ export default function ComponentMeasure({ component, metricKey, metricType }: P | |||
return <span />; | |||
} | |||
return <Measure value={measure.value} metricKey={finalMetricKey} metricType={finalMetricType} />; | |||
return <Measure metricKey={finalMetricKey} metricType={finalMetricType} value={measure.value} />; | |||
} |
@@ -25,6 +25,8 @@ import * as theme from '../../../app/theme'; | |||
import { BranchLike } from '../../../app/types'; | |||
import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon'; | |||
import { translate } from '../../../helpers/l10n'; | |||
function getTooltip(component: Component) { | |||
const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS'; | |||
@@ -77,10 +79,11 @@ export default function ComponentName(props: Props) { | |||
let inner = null; | |||
if (component.refKey && component.qualifier !== 'SVW') { | |||
const branch = rootComponent.qualifier === 'APP' ? { branch: component.branch } : {}; | |||
inner = ( | |||
<Link | |||
to={{ pathname: '/dashboard', query: { id: component.refKey } }} | |||
className="link-with-icon"> | |||
className="link-with-icon" | |||
to={{ pathname: '/dashboard', query: { id: component.refKey, ...branch } }}> | |||
<QualifierIcon qualifier={component.qualifier} /> <span>{name}</span> | |||
</Link> | |||
); | |||
@@ -90,7 +93,7 @@ export default function ComponentName(props: Props) { | |||
Object.assign(query, { selected: component.key }); | |||
} | |||
inner = ( | |||
<Link to={{ pathname: '/code', query }} className="link-with-icon"> | |||
<Link className="link-with-icon" to={{ pathname: '/code', query }}> | |||
<QualifierIcon qualifier={component.qualifier} /> <span>{name}</span> | |||
</Link> | |||
); | |||
@@ -102,5 +105,21 @@ export default function ComponentName(props: Props) { | |||
); | |||
} | |||
if (rootComponent.qualifier === 'APP') { | |||
inner = ( | |||
<> | |||
{inner} | |||
{component.branch ? ( | |||
<> | |||
<LongLivingBranchIcon className="spacer-left little-spacer-right" /> | |||
<span className="note">{component.branch}</span> | |||
</> | |||
) : ( | |||
<span className="spacer-left outline-badge">{translate('branches.main_branch')}</span> | |||
)} | |||
</> | |||
); | |||
} | |||
return <Truncated title={getTooltip(component)}>{inner}</Truncated>; | |||
} |
@@ -21,6 +21,7 @@ | |||
import { Measure } from '../../helpers/measures'; | |||
export interface Component extends Breadcrumb { | |||
branch?: string; | |||
measures?: Measure[]; | |||
path?: string; | |||
refKey?: string; |
@@ -22,12 +22,15 @@ import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import LinkIcon from '../../../components/icons-components/LinkIcon'; | |||
import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | |||
import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon'; | |||
import { splitPath } from '../../../helpers/path'; | |||
import { | |||
getPathUrlAsString, | |||
getBranchLikeUrl, | |||
getLongLivingBranchUrl, | |||
getComponentDrilldownUrlWithSelection | |||
} from '../../../helpers/urls'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: import type { Component, ComponentEnhanced } from '../types'; */ | |||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | |||
@@ -56,23 +59,44 @@ export default class ComponentCell extends React.PureComponent { | |||
const { component } = this.props; | |||
let head = ''; | |||
let tail = component.name; | |||
let branch = null; | |||
if (['DIR', 'FIL', 'UTS'].includes(component.qualifier)) { | |||
const parts = splitPath(component.path); | |||
({ head, tail } = parts); | |||
} | |||
if (this.props.rootComponent.qualifier === 'APP') { | |||
branch = ( | |||
<React.Fragment> | |||
{component.branch ? ( | |||
<React.Fragment> | |||
<LongLivingBranchIcon className="spacer-left little-spacer-right" /> | |||
<span className="note">{component.branch}</span> | |||
</React.Fragment> | |||
) : ( | |||
<span className="spacer-left outline-badge">{translate('branches.main_branch')}</span> | |||
)} | |||
</React.Fragment> | |||
); | |||
} | |||
return ( | |||
<span title={component.refKey || component.key}> | |||
<QualifierIcon qualifier={component.qualifier} /> | |||
| |||
{head.length > 0 && <span className="note">{head}/</span>} | |||
<span>{tail}</span> | |||
{branch} | |||
</span> | |||
); | |||
} | |||
render() { | |||
const { branchLike, component, metric, rootComponent } = this.props; | |||
const to = | |||
this.props.rootComponent.qualifier === 'APP' | |||
? getLongLivingBranchUrl(component.refKey, component.branch) | |||
: getBranchLikeUrl(component.refKey, branchLike); | |||
return ( | |||
<td className="measure-details-component-cell"> | |||
<div className="text-ellipsis"> | |||
@@ -95,7 +119,7 @@ export default class ComponentCell extends React.PureComponent { | |||
<Link | |||
className="link-no-underline" | |||
id={'component-measures-component-link-' + component.key} | |||
to={getBranchLikeUrl(component.refKey, branchLike)}> | |||
to={to}> | |||
<span className="big-spacer-right"> | |||
<LinkIcon /> | |||
</span> |
@@ -18,8 +18,8 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { PullRequest, BranchType, ShortLivingBranch } from '../../../app/types'; | |||
import SourceViewer from '../../../components/SourceViewer/SourceViewer'; | |||
import { fillBranchLike } from '../../../helpers/branches'; | |||
interface Props { | |||
location: { | |||
@@ -54,17 +54,7 @@ export default class App extends React.PureComponent<Props> { | |||
// TODO find a way to avoid creating this fakeBranchLike | |||
// probably the best way would be to drop this page completely | |||
// and redirect to the Code page | |||
let fakeBranchLike: ShortLivingBranch | PullRequest | undefined = undefined; | |||
if (branch) { | |||
fakeBranchLike = { | |||
isMain: false, | |||
mergeBranch: '', | |||
name: branch, | |||
type: BranchType.SHORT | |||
} as ShortLivingBranch; | |||
} else if (pullRequest) { | |||
fakeBranchLike = { base: '', branch: '', key: pullRequest, title: '' } as PullRequest; | |||
} | |||
const fakeBranchLike = fillBranchLike(branch, pullRequest); | |||
return ( | |||
<div className="page page-limited"> |
@@ -62,7 +62,8 @@ import { | |||
isShortLivingBranch, | |||
isSameBranchLike, | |||
getBranchLikeQuery, | |||
isPullRequest | |||
isPullRequest, | |||
fillBranchLike | |||
} from '../../../helpers/branches'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { RawQuery } from '../../../helpers/query'; | |||
@@ -1046,7 +1047,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
<div> | |||
{openIssue ? ( | |||
<IssuesSourceViewer | |||
branchLike={this.props.branchLike} | |||
branchLike={fillBranchLike(openIssue.branch, openIssue.pullRequest)} | |||
loadIssues={this.fetchIssuesForComponent} | |||
locationsNavigator={this.state.locationsNavigator} | |||
onIssueChange={this.handleIssueChange} |
@@ -20,6 +20,7 @@ exports[`renders 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "proj", | |||
}, | |||
} | |||
@@ -155,6 +156,7 @@ exports[`renders with sub-project 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "proj", | |||
}, | |||
} | |||
@@ -177,6 +179,7 @@ exports[`renders with sub-project 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "sub-proj", | |||
}, | |||
} |
@@ -25,9 +25,11 @@ import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter' | |||
import { getApplicationLeak } from '../../../api/application'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import DateFromNow from '../../../components/intl/DateFromNow'; | |||
import { LightComponent, LongLivingBranch } from '../../../app/types'; | |||
interface Props { | |||
component: string; | |||
branch?: LongLivingBranch; | |||
component: LightComponent; | |||
} | |||
interface State { | |||
@@ -44,7 +46,7 @@ export default class ApplicationLeakPeriodLegend extends React.Component<Props, | |||
} | |||
componentWillReceiveProps(nextProps: Props) { | |||
if (nextProps.component !== this.props.component) { | |||
if (nextProps.component.key !== this.props.component.key) { | |||
this.setState({ leaks: undefined }); | |||
} | |||
} | |||
@@ -55,7 +57,10 @@ export default class ApplicationLeakPeriodLegend extends React.Component<Props, | |||
fetchLeaks = () => { | |||
if (!this.state.leaks) { | |||
getApplicationLeak(this.props.component).then( | |||
getApplicationLeak( | |||
this.props.component.key, | |||
this.props.branch ? this.props.branch.name : undefined | |||
).then( | |||
leaks => { | |||
if (this.mounted) { | |||
this.setState({ |
@@ -43,7 +43,11 @@ import { | |||
PROJECT_ACTIVITY_GRAPH, | |||
PROJECT_ACTIVITY_GRAPH_CUSTOM | |||
} from '../../projectActivity/utils'; | |||
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; | |||
import { | |||
isSameBranchLike, | |||
getBranchLikeQuery, | |||
isLongLivingBranch | |||
} from '../../../helpers/branches'; | |||
import { fetchMetrics } from '../../../store/rootActions'; | |||
import { getMetrics } from '../../../store/rootReducer'; | |||
import { BranchLike, Component, Metric } from '../../../app/types'; | |||
@@ -213,7 +217,10 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
return ( | |||
<div className="overview-main page-main"> | |||
{component.qualifier === 'APP' ? ( | |||
<ApplicationQualityGate component={component} /> | |||
<ApplicationQualityGate | |||
branch={isLongLivingBranch(branchLike) ? branchLike : undefined} | |||
component={component} | |||
/> | |||
) : ( | |||
<QualityGate branchLike={branchLike} component={component} measures={measures} /> | |||
)} |
@@ -32,7 +32,11 @@ jest.mock('../../../../api/application', () => ({ | |||
})); | |||
it('renders', async () => { | |||
const wrapper = shallow(<ApplicationLeakPeriodLegend component="foo" />); | |||
const wrapper = shallow( | |||
<ApplicationLeakPeriodLegend | |||
component={{ key: 'foo', organization: 'bar', qualifier: 'APP' }} | |||
/> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
await waitAndUpdate(wrapper); |
@@ -28,11 +28,11 @@ import VulnerabilityIcon from '../../../components/icons-components/Vulnerabilit | |||
import { getMetricName } from '../helpers/metrics'; | |||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { isLongLivingBranch } from '../../../helpers/branches'; | |||
export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> { | |||
renderHeader() { | |||
const { branchLike, component } = this.props; | |||
return ( | |||
<div className="overview-card-header"> | |||
<div className="overview-title"> | |||
@@ -62,7 +62,7 @@ export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> { | |||
} | |||
renderLeak() { | |||
const { component, leakPeriod } = this.props; | |||
const { branchLike, component, leakPeriod } = this.props; | |||
if (!leakPeriod) { | |||
return null; | |||
} | |||
@@ -70,7 +70,10 @@ export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> { | |||
return ( | |||
<div className="overview-domain-leak"> | |||
{component.qualifier === 'APP' ? ( | |||
<ApplicationLeakPeriodLegend component={component.key} /> | |||
<ApplicationLeakPeriodLegend | |||
branch={isLongLivingBranch(branchLike) ? branchLike : undefined} | |||
component={component} | |||
/> | |||
) : ( | |||
<LeakPeriodLegend period={leakPeriod} /> | |||
)} |
@@ -23,10 +23,11 @@ import ApplicationQualityGateProject from './ApplicationQualityGateProject'; | |||
import Level from '../../../components/ui/Level'; | |||
import { getApplicationQualityGate, ApplicationProject } from '../../../api/quality-gates'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { LightComponent, Metric } from '../../../app/types'; | |||
import { LightComponent, Metric, LongLivingBranch } from '../../../app/types'; | |||
import DocTooltip from '../../../components/docs/DocTooltip'; | |||
interface Props { | |||
branch?: LongLivingBranch; | |||
component: LightComponent; | |||
} | |||
@@ -57,10 +58,11 @@ export default class ApplicationQualityGate extends React.PureComponent<Props, S | |||
} | |||
fetchDetails = () => { | |||
const { component } = this.props; | |||
const { branch, component } = this.props; | |||
this.setState({ loading: true }); | |||
getApplicationQualityGate({ | |||
application: component.key, | |||
branch: branch ? branch.name : undefined, | |||
organization: component.organization | |||
}).then( | |||
({ status, projects, metrics }) => { |
@@ -9,6 +9,7 @@ exports[`renders 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} |
@@ -53,6 +53,7 @@ exports[`renders 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -140,6 +141,7 @@ exports[`renders 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "barbar", | |||
}, | |||
} | |||
@@ -227,6 +229,7 @@ exports[`renders 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "bazbaz", | |||
}, | |||
} |
@@ -18,6 +18,7 @@ exports[`renders 1`] = ` | |||
"link": Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
}, |
@@ -18,6 +18,7 @@ exports[`renders 1`] = ` | |||
"link": Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
}, |
@@ -352,6 +352,7 @@ exports[`creates project 4`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "name", | |||
}, | |||
} |
@@ -55,6 +55,7 @@ exports[`should render OK test 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "project:src/file.js", | |||
}, | |||
} |
@@ -168,3 +168,21 @@ export function getBranchLikeQuery(branchLike?: BranchLike): BranchParameters { | |||
return {}; | |||
} | |||
} | |||
// Create branch object from branch name or pull request key | |||
export function fillBranchLike( | |||
branch?: string, | |||
pullRequest?: string | |||
): ShortLivingBranch | PullRequest | undefined { | |||
if (branch) { | |||
return { | |||
isMain: false, | |||
mergeBranch: '', | |||
name: branch, | |||
type: BranchType.SHORT | |||
} as ShortLivingBranch; | |||
} else if (pullRequest) { | |||
return { base: '', branch: '', key: pullRequest, title: '' } as PullRequest; | |||
} | |||
return undefined; | |||
} |
@@ -53,8 +53,8 @@ export function getSonarCloudUrlAsString(location: Location) { | |||
return 'https://sonarcloud.io' + getPathUrlAsString(location); | |||
} | |||
export function getProjectUrl(project: string): Location { | |||
return { pathname: '/dashboard', query: { id: project } }; | |||
export function getProjectUrl(project: string, branch?: string): Location { | |||
return { pathname: '/dashboard', query: { id: project, branch } }; | |||
} | |||
export function getPortfolioUrl(key: string): Location { | |||
@@ -231,7 +231,9 @@ export function getOrganizationUrl(organization: string) { | |||
export function getHomePageUrl(homepage: HomePage) { | |||
switch (homepage.type) { | |||
case HomePageType.Application: | |||
return getProjectUrl(homepage.component); | |||
return homepage.branch | |||
? getProjectUrl(homepage.component, homepage.branch) | |||
: getProjectUrl(homepage.component); | |||
case HomePageType.Project: | |||
return homepage.branch | |||
? getLongLivingBranchUrl(homepage.component, homepage.branch) |
@@ -498,6 +498,8 @@ deletion.page=Deletion | |||
project_deletion.page.description=Delete this project. The operation cannot be undone. | |||
portfolio_deletion.page.description=This portfolio and its sub-portfolios will be deleted. If this portfolio is referenced by other entities, it will be removed from them. Independent entities referenced by this portfolio, such as projects and other top-level portfolios will not be deleted. This operation cannot be undone. | |||
application_deletion.page.description=Delete this application. Application projects will not be deleted. Projects referenced by this application will not be deleted. This operation cannot be undone. | |||
application.branches.help=Easily create Application branches composed of the branches of projects in your application. | |||
application.branches.link=Create Branch | |||
project_branches.page=Branches & Pull Requests | |||
project_branches.page.description=Use this page to manage project branches and pull requests. | |||
project_branches.page.life_time=Short-lived branches and pull requests are permanently deleted after {days} days without analysis. |