projectName: string; | 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 | |||||
); | |||||
} | } |
export function getApplicationQualityGate(data: { | export function getApplicationQualityGate(data: { | ||||
application: string; | application: string; | ||||
branch?: string; | |||||
organization?: string; | organization?: string; | ||||
}): Promise<ApplicationQualityGate> { | }): Promise<ApplicationQualityGate> { | ||||
return getJSON('/api/qualitygates/application_status', data).catch(throwGlobalError); | return getJSON('/api/qualitygates/application_status', data).catch(throwGlobalError); |
import * as React from 'react'; | import * as React from 'react'; | ||||
import * as PropTypes from 'prop-types'; | import * as PropTypes from 'prop-types'; | ||||
import { FormattedMessage } from 'react-intl'; | import { FormattedMessage } from 'react-intl'; | ||||
import { Link } from 'react-router'; | |||||
import ComponentNavBranchesMenu from './ComponentNavBranchesMenu'; | import ComponentNavBranchesMenu from './ComponentNavBranchesMenu'; | ||||
import DocTooltip from '../../../../components/docs/DocTooltip'; | import DocTooltip from '../../../../components/docs/DocTooltip'; | ||||
import { BranchLike, Component } from '../../../types'; | import { BranchLike, Component } from '../../../types'; | ||||
mounted = false; | mounted = false; | ||||
static contextTypes = { | static contextTypes = { | ||||
branchesEnabled: PropTypes.bool.isRequired | |||||
branchesEnabled: PropTypes.bool.isRequired, | |||||
canAdmin: PropTypes.bool.isRequired | |||||
}; | }; | ||||
state: State = { | state: 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() { | render() { | ||||
const { branchLikes, currentBranchLike } = this.props; | const { branchLikes, currentBranchLike } = this.props; | ||||
const { configuration } = this.props.component; | |||||
const { configuration, breadcrumbs } = this.props.component; | |||||
if (isSonarCloud() && !this.context.branchesEnabled) { | if (isSonarCloud() && !this.context.branchesEnabled) { | ||||
return null; | return null; | ||||
} | } | ||||
const displayName = getBranchLikeDisplayName(currentBranchLike); | const displayName = getBranchLikeDisplayName(currentBranchLike); | ||||
const isApp = breadcrumbs && breadcrumbs[0] && breadcrumbs[0].qualifier === 'APP'; | |||||
if (!this.context.branchesEnabled) { | |||||
if (isApp && branchLikes.length < 2) { | |||||
return ( | return ( | ||||
<div className="navbar-context-branches"> | <div className="navbar-context-branches"> | ||||
<BranchIcon | <BranchIcon | ||||
fill={theme.gray80} | fill={theme.gray80} | ||||
/> | /> | ||||
<span className="note">{displayName}</span> | <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> | </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 ( | return ( |
renderExtension = ({ key, name }: Extension, isAdmin: boolean) => { | renderExtension = ({ key, name }: Extension, isAdmin: boolean) => { | ||||
const pathname = isAdmin ? `/project/admin/extension/${key}` : `/project/extension/${key}`; | const pathname = isAdmin ? `/project/admin/extension/${key}` : `/project/extension/${key}`; | ||||
const query = { id: this.props.component.key, qualifier: this.props.component.qualifier }; | |||||
return ( | return ( | ||||
<li key={key}> | <li key={key}> | ||||
<Link to={{ pathname, query: { id: this.props.component.key } }} activeClassName="active"> | |||||
<Link activeClassName="active" to={{ pathname, query }}> | |||||
{name} | {name} | ||||
</Link> | </Link> | ||||
</li> | </li> |
if (component.qualifier === 'VW' || component.qualifier === 'SVW') { | if (component.qualifier === 'VW' || component.qualifier === 'SVW') { | ||||
currentPage = { type: HomePageType.Portfolio, component: component.key }; | currentPage = { type: HomePageType.Portfolio, component: component.key }; | ||||
} else if (component.qualifier === 'APP') { | } 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') { | } else if (component.qualifier === 'TRK') { | ||||
// when home page is set to the default branch of a project, its name is returned as `undefined` | // 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; | const branch = isLongLivingBranch(branchLike) ? branchLike.name : undefined; |
component={component} | component={component} | ||||
currentBranchLike={mainBranch} | currentBranchLike={mainBranch} | ||||
/>, | />, | ||||
{ context: { branchesEnabled: true } } | |||||
{ context: { branchesEnabled: true, canAdmin: true } } | |||||
) | ) | ||||
).toMatchSnapshot(); | ).toMatchSnapshot(); | ||||
}); | }); | ||||
component={component} | component={component} | ||||
currentBranchLike={branch} | currentBranchLike={branch} | ||||
/>, | />, | ||||
{ context: { branchesEnabled: true } } | |||||
{ context: { branchesEnabled: true, canAdmin: true } } | |||||
) | ) | ||||
).toMatchSnapshot(); | ).toMatchSnapshot(); | ||||
}); | }); | ||||
component={component} | component={component} | ||||
currentBranchLike={pullRequest} | currentBranchLike={pullRequest} | ||||
/>, | />, | ||||
{ context: { branchesEnabled: true } } | |||||
{ context: { branchesEnabled: true, canAdmin: true } } | |||||
) | ) | ||||
).toMatchSnapshot(); | ).toMatchSnapshot(); | ||||
}); | }); | ||||
component={component} | component={component} | ||||
currentBranchLike={mainBranch} | currentBranchLike={mainBranch} | ||||
/>, | />, | ||||
{ context: { branchesEnabled: true } } | |||||
{ context: { branchesEnabled: true, canAdmin: true } } | |||||
); | ); | ||||
expect(wrapper.find('Toggler').prop('open')).toBe(false); | expect(wrapper.find('Toggler').prop('open')).toBe(false); | ||||
click(wrapper.find('a')); | click(wrapper.find('a')); | ||||
component={component} | component={component} | ||||
currentBranchLike={mainBranch} | currentBranchLike={mainBranch} | ||||
/>, | />, | ||||
{ context: { branchesEnabled: true } } | |||||
{ context: { branchesEnabled: true, canAdmin: true } } | |||||
); | ); | ||||
expect(wrapper.find('DocTooltip')).toMatchSnapshot(); | expect(wrapper.find('DocTooltip')).toMatchSnapshot(); | ||||
}); | }); | ||||
component={component} | component={component} | ||||
currentBranchLike={mainBranch} | currentBranchLike={mainBranch} | ||||
/>, | />, | ||||
{ context: { branchesEnabled: false } } | |||||
{ context: { branchesEnabled: false, canAdmin: true } } | |||||
); | ); | ||||
expect(wrapper.find('DocTooltip')).toMatchSnapshot(); | expect(wrapper.find('DocTooltip')).toMatchSnapshot(); | ||||
}); | }); | ||||
component={component} | component={component} | ||||
currentBranchLike={mainBranch} | currentBranchLike={mainBranch} | ||||
/>, | />, | ||||
{ context: { branchesEnabled: false, onSonarCloud: true } } | |||||
{ context: { branchesEnabled: false, onSonarCloud: true, canAdmin: true } } | |||||
); | ); | ||||
expect(wrapper.type()).toBeNull(); | expect(wrapper.type()).toBeNull(); | ||||
}); | }); |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "component", | "id": "component", | ||||
}, | }, | ||||
} | } |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "my-project", | "id": "my-project", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "my-project", | "id": "my-project", | ||||
}, | }, | ||||
} | } |
"pathname": "/project/extension/component-foo", | "pathname": "/project/extension/component-foo", | ||||
"query": Object { | "query": Object { | ||||
"id": "foo", | "id": "foo", | ||||
"qualifier": "TRK", | |||||
}, | }, | ||||
} | } | ||||
} | } | ||||
"pathname": "/project/admin/extension/foo", | "pathname": "/project/admin/extension/foo", | ||||
"query": Object { | "query": Object { | ||||
"id": "foo", | "id": "foo", | ||||
"qualifier": "TRK", | |||||
}, | }, | ||||
} | } | ||||
} | } | ||||
"pathname": "/project/extension/component-foo", | "pathname": "/project/extension/component-foo", | ||||
"query": Object { | "query": Object { | ||||
"id": "foo", | "id": "foo", | ||||
"qualifier": "TRK", | |||||
}, | }, | ||||
} | } | ||||
} | } | ||||
"pathname": "/project/extension/component-bar", | "pathname": "/project/extension/component-bar", | ||||
"query": Object { | "query": Object { | ||||
"id": "foo", | "id": "foo", | ||||
"qualifier": "TRK", | |||||
}, | }, | ||||
} | } | ||||
} | } | ||||
"pathname": "/project/admin/extension/foo", | "pathname": "/project/admin/extension/foo", | ||||
"query": Object { | "query": Object { | ||||
"id": "foo", | "id": "foo", | ||||
"qualifier": "TRK", | |||||
}, | }, | ||||
} | } | ||||
} | } | ||||
"pathname": "/project/admin/extension/bar", | "pathname": "/project/admin/extension/bar", | ||||
"query": Object { | "query": Object { | ||||
"id": "foo", | "id": "foo", | ||||
"qualifier": "TRK", | |||||
}, | }, | ||||
} | } | ||||
} | } |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "qwe", | "id": "qwe", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } |
} | } | ||||
export type HomePage = | export type HomePage = | ||||
| { type: HomePageType.Application; component: string } | |||||
| { type: HomePageType.Application; branch: string | undefined; component: string } | |||||
| { type: HomePageType.Issues } | | { type: HomePageType.Issues } | ||||
| { type: HomePageType.MyIssues } | | { type: HomePageType.MyIssues } | ||||
| { type: HomePageType.MyProjects } | | { type: HomePageType.MyProjects } | ||||
assigneeLogin?: string; | assigneeLogin?: string; | ||||
assigneeName?: string; | assigneeName?: string; | ||||
author?: string; | author?: string; | ||||
branch?: string; | |||||
comments?: IssueComment[]; | comments?: IssueComment[]; | ||||
component: string; | component: string; | ||||
componentLongName: string; | componentLongName: string; | ||||
projectName: string; | projectName: string; | ||||
projectOrganization: string; | projectOrganization: string; | ||||
projectUuid: string; | projectUuid: string; | ||||
pullRequest?: string; | |||||
resolution?: string; | resolution?: string; | ||||
rule: string; | rule: string; | ||||
ruleName: string; | ruleName: string; |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } |
return <span />; | return <span />; | ||||
} | } | ||||
return <Measure value={measure.value} metricKey={finalMetricKey} metricType={finalMetricType} />; | |||||
return <Measure metricKey={finalMetricKey} metricType={finalMetricType} value={measure.value} />; | |||||
} | } |
import { BranchLike } from '../../../app/types'; | import { BranchLike } from '../../../app/types'; | ||||
import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | ||||
import { getBranchLikeQuery } from '../../../helpers/branches'; | import { getBranchLikeQuery } from '../../../helpers/branches'; | ||||
import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
function getTooltip(component: Component) { | function getTooltip(component: Component) { | ||||
const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS'; | const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS'; | ||||
let inner = null; | let inner = null; | ||||
if (component.refKey && component.qualifier !== 'SVW') { | if (component.refKey && component.qualifier !== 'SVW') { | ||||
const branch = rootComponent.qualifier === 'APP' ? { branch: component.branch } : {}; | |||||
inner = ( | inner = ( | ||||
<Link | <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> | <QualifierIcon qualifier={component.qualifier} /> <span>{name}</span> | ||||
</Link> | </Link> | ||||
); | ); | ||||
Object.assign(query, { selected: component.key }); | Object.assign(query, { selected: component.key }); | ||||
} | } | ||||
inner = ( | 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> | <QualifierIcon qualifier={component.qualifier} /> <span>{name}</span> | ||||
</Link> | </Link> | ||||
); | ); | ||||
); | ); | ||||
} | } | ||||
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>; | return <Truncated title={getTooltip(component)}>{inner}</Truncated>; | ||||
} | } |
import { Measure } from '../../helpers/measures'; | import { Measure } from '../../helpers/measures'; | ||||
export interface Component extends Breadcrumb { | export interface Component extends Breadcrumb { | ||||
branch?: string; | |||||
measures?: Measure[]; | measures?: Measure[]; | ||||
path?: string; | path?: string; | ||||
refKey?: string; | refKey?: string; |
import { Link } from 'react-router'; | import { Link } from 'react-router'; | ||||
import LinkIcon from '../../../components/icons-components/LinkIcon'; | import LinkIcon from '../../../components/icons-components/LinkIcon'; | ||||
import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | ||||
import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon'; | |||||
import { splitPath } from '../../../helpers/path'; | import { splitPath } from '../../../helpers/path'; | ||||
import { | import { | ||||
getPathUrlAsString, | getPathUrlAsString, | ||||
getBranchLikeUrl, | getBranchLikeUrl, | ||||
getLongLivingBranchUrl, | |||||
getComponentDrilldownUrlWithSelection | getComponentDrilldownUrlWithSelection | ||||
} from '../../../helpers/urls'; | } from '../../../helpers/urls'; | ||||
import { translate } from '../../../helpers/l10n'; | |||||
/*:: import type { Component, ComponentEnhanced } from '../types'; */ | /*:: import type { Component, ComponentEnhanced } from '../types'; */ | ||||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | /*:: import type { Metric } from '../../../store/metrics/actions'; */ | ||||
const { component } = this.props; | const { component } = this.props; | ||||
let head = ''; | let head = ''; | ||||
let tail = component.name; | let tail = component.name; | ||||
let branch = null; | |||||
if (['DIR', 'FIL', 'UTS'].includes(component.qualifier)) { | if (['DIR', 'FIL', 'UTS'].includes(component.qualifier)) { | ||||
const parts = splitPath(component.path); | const parts = splitPath(component.path); | ||||
({ head, tail } = parts); | ({ 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 ( | return ( | ||||
<span title={component.refKey || component.key}> | <span title={component.refKey || component.key}> | ||||
<QualifierIcon qualifier={component.qualifier} /> | <QualifierIcon qualifier={component.qualifier} /> | ||||
| | ||||
{head.length > 0 && <span className="note">{head}/</span>} | {head.length > 0 && <span className="note">{head}/</span>} | ||||
<span>{tail}</span> | <span>{tail}</span> | ||||
{branch} | |||||
</span> | </span> | ||||
); | ); | ||||
} | } | ||||
render() { | render() { | ||||
const { branchLike, component, metric, rootComponent } = this.props; | const { branchLike, component, metric, rootComponent } = this.props; | ||||
const to = | |||||
this.props.rootComponent.qualifier === 'APP' | |||||
? getLongLivingBranchUrl(component.refKey, component.branch) | |||||
: getBranchLikeUrl(component.refKey, branchLike); | |||||
return ( | return ( | ||||
<td className="measure-details-component-cell"> | <td className="measure-details-component-cell"> | ||||
<div className="text-ellipsis"> | <div className="text-ellipsis"> | ||||
<Link | <Link | ||||
className="link-no-underline" | className="link-no-underline" | ||||
id={'component-measures-component-link-' + component.key} | id={'component-measures-component-link-' + component.key} | ||||
to={getBranchLikeUrl(component.refKey, branchLike)}> | |||||
to={to}> | |||||
<span className="big-spacer-right"> | <span className="big-spacer-right"> | ||||
<LinkIcon /> | <LinkIcon /> | ||||
</span> | </span> |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { PullRequest, BranchType, ShortLivingBranch } from '../../../app/types'; | |||||
import SourceViewer from '../../../components/SourceViewer/SourceViewer'; | import SourceViewer from '../../../components/SourceViewer/SourceViewer'; | ||||
import { fillBranchLike } from '../../../helpers/branches'; | |||||
interface Props { | interface Props { | ||||
location: { | location: { | ||||
// TODO find a way to avoid creating this fakeBranchLike | // TODO find a way to avoid creating this fakeBranchLike | ||||
// probably the best way would be to drop this page completely | // probably the best way would be to drop this page completely | ||||
// and redirect to the Code page | // 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 ( | return ( | ||||
<div className="page page-limited"> | <div className="page page-limited"> |
isShortLivingBranch, | isShortLivingBranch, | ||||
isSameBranchLike, | isSameBranchLike, | ||||
getBranchLikeQuery, | getBranchLikeQuery, | ||||
isPullRequest | |||||
isPullRequest, | |||||
fillBranchLike | |||||
} from '../../../helpers/branches'; | } from '../../../helpers/branches'; | ||||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | import { translate, translateWithParameters } from '../../../helpers/l10n'; | ||||
import { RawQuery } from '../../../helpers/query'; | import { RawQuery } from '../../../helpers/query'; | ||||
<div> | <div> | ||||
{openIssue ? ( | {openIssue ? ( | ||||
<IssuesSourceViewer | <IssuesSourceViewer | ||||
branchLike={this.props.branchLike} | |||||
branchLike={fillBranchLike(openIssue.branch, openIssue.pullRequest)} | |||||
loadIssues={this.fetchIssuesForComponent} | loadIssues={this.fetchIssuesForComponent} | ||||
locationsNavigator={this.state.locationsNavigator} | locationsNavigator={this.state.locationsNavigator} | ||||
onIssueChange={this.handleIssueChange} | onIssueChange={this.handleIssueChange} |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "proj", | "id": "proj", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "proj", | "id": "proj", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "sub-proj", | "id": "sub-proj", | ||||
}, | }, | ||||
} | } |
import { getApplicationLeak } from '../../../api/application'; | import { getApplicationLeak } from '../../../api/application'; | ||||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | import { translate, translateWithParameters } from '../../../helpers/l10n'; | ||||
import DateFromNow from '../../../components/intl/DateFromNow'; | import DateFromNow from '../../../components/intl/DateFromNow'; | ||||
import { LightComponent, LongLivingBranch } from '../../../app/types'; | |||||
interface Props { | interface Props { | ||||
component: string; | |||||
branch?: LongLivingBranch; | |||||
component: LightComponent; | |||||
} | } | ||||
interface State { | interface State { | ||||
} | } | ||||
componentWillReceiveProps(nextProps: Props) { | componentWillReceiveProps(nextProps: Props) { | ||||
if (nextProps.component !== this.props.component) { | |||||
if (nextProps.component.key !== this.props.component.key) { | |||||
this.setState({ leaks: undefined }); | this.setState({ leaks: undefined }); | ||||
} | } | ||||
} | } | ||||
fetchLeaks = () => { | fetchLeaks = () => { | ||||
if (!this.state.leaks) { | if (!this.state.leaks) { | ||||
getApplicationLeak(this.props.component).then( | |||||
getApplicationLeak( | |||||
this.props.component.key, | |||||
this.props.branch ? this.props.branch.name : undefined | |||||
).then( | |||||
leaks => { | leaks => { | ||||
if (this.mounted) { | if (this.mounted) { | ||||
this.setState({ | this.setState({ |
PROJECT_ACTIVITY_GRAPH, | PROJECT_ACTIVITY_GRAPH, | ||||
PROJECT_ACTIVITY_GRAPH_CUSTOM | PROJECT_ACTIVITY_GRAPH_CUSTOM | ||||
} from '../../projectActivity/utils'; | } from '../../projectActivity/utils'; | ||||
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; | |||||
import { | |||||
isSameBranchLike, | |||||
getBranchLikeQuery, | |||||
isLongLivingBranch | |||||
} from '../../../helpers/branches'; | |||||
import { fetchMetrics } from '../../../store/rootActions'; | import { fetchMetrics } from '../../../store/rootActions'; | ||||
import { getMetrics } from '../../../store/rootReducer'; | import { getMetrics } from '../../../store/rootReducer'; | ||||
import { BranchLike, Component, Metric } from '../../../app/types'; | import { BranchLike, Component, Metric } from '../../../app/types'; | ||||
return ( | return ( | ||||
<div className="overview-main page-main"> | <div className="overview-main page-main"> | ||||
{component.qualifier === 'APP' ? ( | {component.qualifier === 'APP' ? ( | ||||
<ApplicationQualityGate component={component} /> | |||||
<ApplicationQualityGate | |||||
branch={isLongLivingBranch(branchLike) ? branchLike : undefined} | |||||
component={component} | |||||
/> | |||||
) : ( | ) : ( | ||||
<QualityGate branchLike={branchLike} component={component} measures={measures} /> | <QualityGate branchLike={branchLike} component={component} measures={measures} /> | ||||
)} | )} |
})); | })); | ||||
it('renders', async () => { | it('renders', async () => { | ||||
const wrapper = shallow(<ApplicationLeakPeriodLegend component="foo" />); | |||||
const wrapper = shallow( | |||||
<ApplicationLeakPeriodLegend | |||||
component={{ key: 'foo', organization: 'bar', qualifier: 'APP' }} | |||||
/> | |||||
); | |||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); |
import { getMetricName } from '../helpers/metrics'; | import { getMetricName } from '../helpers/metrics'; | ||||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | import { getComponentDrilldownUrl } from '../../../helpers/urls'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { isLongLivingBranch } from '../../../helpers/branches'; | |||||
export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> { | export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> { | ||||
renderHeader() { | renderHeader() { | ||||
const { branchLike, component } = this.props; | const { branchLike, component } = this.props; | ||||
return ( | return ( | ||||
<div className="overview-card-header"> | <div className="overview-card-header"> | ||||
<div className="overview-title"> | <div className="overview-title"> | ||||
} | } | ||||
renderLeak() { | renderLeak() { | ||||
const { component, leakPeriod } = this.props; | |||||
const { branchLike, component, leakPeriod } = this.props; | |||||
if (!leakPeriod) { | if (!leakPeriod) { | ||||
return null; | return null; | ||||
} | } | ||||
return ( | return ( | ||||
<div className="overview-domain-leak"> | <div className="overview-domain-leak"> | ||||
{component.qualifier === 'APP' ? ( | {component.qualifier === 'APP' ? ( | ||||
<ApplicationLeakPeriodLegend component={component.key} /> | |||||
<ApplicationLeakPeriodLegend | |||||
branch={isLongLivingBranch(branchLike) ? branchLike : undefined} | |||||
component={component} | |||||
/> | |||||
) : ( | ) : ( | ||||
<LeakPeriodLegend period={leakPeriod} /> | <LeakPeriodLegend period={leakPeriod} /> | ||||
)} | )} |
import Level from '../../../components/ui/Level'; | import Level from '../../../components/ui/Level'; | ||||
import { getApplicationQualityGate, ApplicationProject } from '../../../api/quality-gates'; | import { getApplicationQualityGate, ApplicationProject } from '../../../api/quality-gates'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { LightComponent, Metric } from '../../../app/types'; | |||||
import { LightComponent, Metric, LongLivingBranch } from '../../../app/types'; | |||||
import DocTooltip from '../../../components/docs/DocTooltip'; | import DocTooltip from '../../../components/docs/DocTooltip'; | ||||
interface Props { | interface Props { | ||||
branch?: LongLivingBranch; | |||||
component: LightComponent; | component: LightComponent; | ||||
} | } | ||||
} | } | ||||
fetchDetails = () => { | fetchDetails = () => { | ||||
const { component } = this.props; | |||||
const { branch, component } = this.props; | |||||
this.setState({ loading: true }); | this.setState({ loading: true }); | ||||
getApplicationQualityGate({ | getApplicationQualityGate({ | ||||
application: component.key, | application: component.key, | ||||
branch: branch ? branch.name : undefined, | |||||
organization: component.organization | organization: component.organization | ||||
}).then( | }).then( | ||||
({ status, projects, metrics }) => { | ({ status, projects, metrics }) => { |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "barbar", | "id": "barbar", | ||||
}, | }, | ||||
} | } | ||||
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "bazbaz", | "id": "bazbaz", | ||||
}, | }, | ||||
} | } |
"link": Object { | "link": Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
}, | }, |
"link": Object { | "link": Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "foo", | "id": "foo", | ||||
}, | }, | ||||
}, | }, |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "name", | "id": "name", | ||||
}, | }, | ||||
} | } |
Object { | Object { | ||||
"pathname": "/dashboard", | "pathname": "/dashboard", | ||||
"query": Object { | "query": Object { | ||||
"branch": undefined, | |||||
"id": "project:src/file.js", | "id": "project:src/file.js", | ||||
}, | }, | ||||
} | } |
return {}; | 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; | |||||
} |
return 'https://sonarcloud.io' + getPathUrlAsString(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 { | export function getPortfolioUrl(key: string): Location { | ||||
export function getHomePageUrl(homepage: HomePage) { | export function getHomePageUrl(homepage: HomePage) { | ||||
switch (homepage.type) { | switch (homepage.type) { | ||||
case HomePageType.Application: | case HomePageType.Application: | ||||
return getProjectUrl(homepage.component); | |||||
return homepage.branch | |||||
? getProjectUrl(homepage.component, homepage.branch) | |||||
: getProjectUrl(homepage.component); | |||||
case HomePageType.Project: | case HomePageType.Project: | ||||
return homepage.branch | return homepage.branch | ||||
? getLongLivingBranchUrl(homepage.component, homepage.branch) | ? getLongLivingBranchUrl(homepage.component, homepage.branch) |
project_deletion.page.description=Delete this project. The operation cannot be undone. | 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. | 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_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=Branches & Pull Requests | ||||
project_branches.page.description=Use this page to manage project branches and 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. | project_branches.page.life_time=Short-lived branches and pull requests are permanently deleted after {days} days without analysis. |