aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/js/api/application.ts10
-rw-r--r--server/sonar-web/src/main/js/api/quality-gates.ts1
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx74
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx3
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx3
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx14
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap8
-rw-r--r--server/sonar-web/src/main/js/app/types.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx25
-rw-r--r--server/sonar-web/src/main/js/apps/code/types.ts1
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js26
-rw-r--r--server/sonar-web/src/main/js/apps/component/components/App.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/App.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/helpers/branches.ts18
-rw-r--r--server/sonar-web/src/main/js/helpers/urls.ts8
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
34 files changed, 220 insertions, 63 deletions
diff --git a/server/sonar-web/src/main/js/api/application.ts b/server/sonar-web/src/main/js/api/application.ts
index 5821d5705be..6fb3876a486 100644
--- a/server/sonar-web/src/main/js/api/application.ts
+++ b/server/sonar-web/src/main/js/api/application.ts
@@ -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
+ );
}
diff --git a/server/sonar-web/src/main/js/api/quality-gates.ts b/server/sonar-web/src/main/js/api/quality-gates.ts
index d2f212bd457..af49578283e 100644
--- a/server/sonar-web/src/main/js/api/quality-gates.ts
+++ b/server/sonar-web/src/main/js/api/quality-gates.ts
@@ -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);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
index 8cba10a5dbc..d02389be63d 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
@@ -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 (
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
index 1f3045cfc52..53009cc4d40 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
@@ -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>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
index 6b52b5eefbf..7b2f2e2c221 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
@@ -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;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
index 9a805b74c82..3fd20dbc41e 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
@@ -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();
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap
index b4c9f37a5d3..25528a5523c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap
@@ -13,6 +13,7 @@ exports[`renders main branch 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "component",
},
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
index 4d85fb56b11..b8498689bda 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
@@ -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",
},
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
index 3abe30c25f4..a62c3ce9e0e 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
@@ -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",
},
}
}
diff --git a/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap b/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
index 4ad1c1e2e2d..0a9d6781bf0 100644
--- a/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
+++ b/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
@@ -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",
},
}
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index 11e29502c06..75ac42d3237 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -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;
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap
index 90b25b16cf1..11b25e6dd9d 100644
--- a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap
@@ -25,6 +25,7 @@ exports[`should match snapshot 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "foo",
},
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap
index 88328a93b61..b054cb915e8 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap
@@ -20,6 +20,7 @@ exports[`renders correctly 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "foo",
},
}
diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx
index bc900211a0e..bb3cbe506a2 100644
--- a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx
@@ -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} />;
}
diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
index 57473532b8a..391c1862c04 100644
--- a/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
@@ -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>;
}
diff --git a/server/sonar-web/src/main/js/apps/code/types.ts b/server/sonar-web/src/main/js/apps/code/types.ts
index 3a226f8127d..a0b6459a3d3 100644
--- a/server/sonar-web/src/main/js/apps/code/types.ts
+++ b/server/sonar-web/src/main/js/apps/code/types.ts
@@ -21,6 +21,7 @@
import { Measure } from '../../helpers/measures';
export interface Component extends Breadcrumb {
+ branch?: string;
measures?: Measure[];
path?: string;
refKey?: string;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js
index 9b2ce2e9b6e..fc268fa3425 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js
@@ -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} />
&nbsp;
{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>
diff --git a/server/sonar-web/src/main/js/apps/component/components/App.tsx b/server/sonar-web/src/main/js/apps/component/components/App.tsx
index 71fc859ea2f..d70fc1c92e6 100644
--- a/server/sonar-web/src/main/js/apps/component/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/component/components/App.tsx
@@ -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">
diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.tsx b/server/sonar-web/src/main/js/apps/issues/components/App.tsx
index 2ae38d862de..4e22af0bfd3 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/App.tsx
@@ -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}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap
index 522da78f5a8..c0182df2b97 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap
@@ -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",
},
}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx
index eb230682fcb..0ac7464bcb3 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx
@@ -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({
diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
index 68a7d1bbb1d..df32445c412 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
@@ -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} />
)}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx
index 27a71ac1851..1dba9c67b4b 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx
@@ -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);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx b/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx
index 3ce147ba718..4715c66a2ff 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx
@@ -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} />
)}
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx
index d50bc8a42f1..e4771d74e76 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx
@@ -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 }) => {
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap
index b4c85030625..8af25b25049 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap
@@ -9,6 +9,7 @@ exports[`renders 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "foo",
},
}
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap
index ceb66108e3c..736ab248060 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap
@@ -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",
},
}
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap
index c1aac43c7d7..38408926a5b 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap
@@ -18,6 +18,7 @@ exports[`renders 1`] = `
"link": Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "foo",
},
},
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap
index 0c07a84920e..fd133225a39 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap
@@ -18,6 +18,7 @@ exports[`renders 1`] = `
"link": Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "foo",
},
},
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
index 98ca96135f8..aead0a2ce17 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
@@ -352,6 +352,7 @@ exports[`creates project 4`] = `
Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "name",
},
}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap
index b053a1bf9af..5552a961caa 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap
@@ -55,6 +55,7 @@ exports[`should render OK test 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "project:src/file.js",
},
}
diff --git a/server/sonar-web/src/main/js/helpers/branches.ts b/server/sonar-web/src/main/js/helpers/branches.ts
index 6b4507eddcd..86dc7e10671 100644
--- a/server/sonar-web/src/main/js/helpers/branches.ts
+++ b/server/sonar-web/src/main/js/helpers/branches.ts
@@ -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;
+}
diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts
index 93e531c52df..4d17bde0b09 100644
--- a/server/sonar-web/src/main/js/helpers/urls.ts
+++ b/server/sonar-web/src/main/js/helpers/urls.ts
@@ -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)
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 65e77118171..79d87fab775 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -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.