Browse Source

SONAR-10813 Add project branches

tags/7.5
Pascal Mugnier 6 years ago
parent
commit
e5827b3671
34 changed files with 220 additions and 63 deletions
  1. 8
    2
      server/sonar-web/src/main/js/api/application.ts
  2. 1
    0
      server/sonar-web/src/main/js/api/quality-gates.ts
  3. 56
    18
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
  4. 2
    1
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
  5. 2
    1
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
  6. 7
    7
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
  7. 1
    0
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap
  8. 2
    0
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
  9. 6
    0
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
  10. 8
    0
      server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
  11. 3
    1
      server/sonar-web/src/main/js/app/types.ts
  12. 1
    0
      server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap
  13. 1
    0
      server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap
  14. 1
    1
      server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx
  15. 22
    3
      server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
  16. 1
    0
      server/sonar-web/src/main/js/apps/code/types.ts
  17. 25
    1
      server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js
  18. 2
    12
      server/sonar-web/src/main/js/apps/component/components/App.tsx
  19. 3
    2
      server/sonar-web/src/main/js/apps/issues/components/App.tsx
  20. 3
    0
      server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap
  21. 8
    3
      server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx
  22. 9
    2
      server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
  23. 5
    1
      server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx
  24. 6
    3
      server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx
  25. 4
    2
      server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx
  26. 1
    0
      server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap
  27. 3
    0
      server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap
  28. 1
    0
      server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap
  29. 1
    0
      server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap
  30. 1
    0
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
  31. 1
    0
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap
  32. 18
    0
      server/sonar-web/src/main/js/helpers/branches.ts
  33. 5
    3
      server/sonar-web/src/main/js/helpers/urls.ts
  34. 2
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 8
- 2
server/sonar-web/src/main/js/api/application.ts View File

@@ -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
);
}

+ 1
- 0
server/sonar-web/src/main/js/api/quality-gates.ts View File

@@ -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);

+ 56
- 18
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx View File

@@ -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 (

+ 2
- 1
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx View File

@@ -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>

+ 2
- 1
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx View File

@@ -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;

+ 7
- 7
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx View File

@@ -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();
});

+ 1
- 0
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap View File

@@ -13,6 +13,7 @@ exports[`renders main branch 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "component",
},
}

+ 2
- 0
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap View File

@@ -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",
},
}

+ 6
- 0
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap View File

@@ -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",
},
}
}

+ 8
- 0
server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap View File

@@ -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",
},
}

+ 3
- 1
server/sonar-web/src/main/js/app/types.ts View File

@@ -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;

+ 1
- 0
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap View File

@@ -25,6 +25,7 @@ exports[`should match snapshot 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}

+ 1
- 0
server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap View File

@@ -20,6 +20,7 @@ exports[`renders correctly 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}

+ 1
- 1
server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx View File

@@ -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} />;
}

+ 22
- 3
server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx View File

@@ -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>;
}

+ 1
- 0
server/sonar-web/src/main/js/apps/code/types.ts View File

@@ -21,6 +21,7 @@
import { Measure } from '../../helpers/measures';

export interface Component extends Breadcrumb {
branch?: string;
measures?: Measure[];
path?: string;
refKey?: string;

+ 25
- 1
server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js View File

@@ -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>

+ 2
- 12
server/sonar-web/src/main/js/apps/component/components/App.tsx View File

@@ -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">

+ 3
- 2
server/sonar-web/src/main/js/apps/issues/components/App.tsx View File

@@ -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}

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap View File

@@ -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",
},
}

+ 8
- 3
server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx View File

@@ -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({

+ 9
- 2
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx View File

@@ -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} />
)}

+ 5
- 1
server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx View File

@@ -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);

+ 6
- 3
server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx View File

@@ -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} />
)}

+ 4
- 2
server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx View File

@@ -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 }) => {

+ 1
- 0
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap View File

@@ -9,6 +9,7 @@ exports[`renders 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}

+ 3
- 0
server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap View File

@@ -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",
},
}

+ 1
- 0
server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap View File

@@ -18,6 +18,7 @@ exports[`renders 1`] = `
"link": Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
},

+ 1
- 0
server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap View File

@@ -18,6 +18,7 @@ exports[`renders 1`] = `
"link": Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
},

+ 1
- 0
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap View File

@@ -352,6 +352,7 @@ exports[`creates project 4`] = `
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "name",
},
}

+ 1
- 0
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap View File

@@ -55,6 +55,7 @@ exports[`should render OK test 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "project:src/file.js",
},
}

+ 18
- 0
server/sonar-web/src/main/js/helpers/branches.ts View File

@@ -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;
}

+ 5
- 3
server/sonar-web/src/main/js/helpers/urls.ts View File

@@ -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)

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -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.

Loading…
Cancel
Save