diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2018-03-12 12:06:11 +0100 |
---|---|---|
committer | Teryk Bellahsene <teryk@users.noreply.github.com> | 2018-03-13 14:05:36 +0100 |
commit | 913c82c8772fd4747626a1fbe665ccda2e5ca9f1 (patch) | |
tree | d48784851df80905ce125cc60ac8aec8570751a9 /server/sonar-web/src/main/js/apps | |
parent | 751e4000e40a4af66b80767d632b1bef64dc5647 (diff) | |
download | sonarqube-913c82c8772fd4747626a1fbe665ccda2e5ca9f1.tar.gz sonarqube-913c82c8772fd4747626a1fbe665ccda2e5ca9f1.zip |
SONAR-10374 Support pull request in the web app
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
94 files changed, 865 insertions, 494 deletions
diff --git a/server/sonar-web/src/main/js/apps/about/actions.js b/server/sonar-web/src/main/js/apps/about/actions.js index 2c63a8553b9..a60d730c30a 100644 --- a/server/sonar-web/src/main/js/apps/about/actions.js +++ b/server/sonar-web/src/main/js/apps/about/actions.js @@ -23,7 +23,7 @@ import { receiveValues } from '../settings/store/values/actions'; export const fetchAboutPageSettings = () => dispatch => { const keys = ['sonar.lf.aboutText']; - return getValues(keys.join()).then(values => { + return getValues({ keys: keys.join() }).then(values => { dispatch(receiveValues(values)); }); }; diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap index 2fb0f7c15d1..33c554244f1 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap +++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap @@ -23,7 +23,6 @@ exports[`should match snapshot 1`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "foo", }, } diff --git a/server/sonar-web/src/main/js/apps/account/organizations/actions.ts b/server/sonar-web/src/main/js/apps/account/organizations/actions.ts index 89dbf7c9705..4361e0d0849 100644 --- a/server/sonar-web/src/main/js/apps/account/organizations/actions.ts +++ b/server/sonar-web/src/main/js/apps/account/organizations/actions.ts @@ -30,7 +30,7 @@ export const fetchMyOrganizations = () => (dispatch: Dispatch<any>) => { }; export const fetchIfAnyoneCanCreateOrganizations = () => (dispatch: Dispatch<any>) => { - return getValues('sonar.organizations.anyoneCanCreate').then(values => { + return getValues({ keys: 'sonar.organizations.anyoneCanCreate' }).then(values => { dispatch(receiveValues(values, undefined)); }); }; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx index 61fba1730da..a81919215b1 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx @@ -23,9 +23,16 @@ import TaskType from './TaskType'; import { Task } from '../types'; import QualifierIcon from '../../../components/shared/QualifierIcon'; import Organization from '../../../components/shared/Organization'; -import { getProjectUrl } from '../../../helpers/urls'; +import { + getProjectUrl, + getShortLivingBranchUrl, + getLongLivingBranchUrl, + getPullRequestUrl +} from '../../../helpers/urls'; import ShortLivingBranchIcon from '../../../components/icons-components/ShortLivingBranchIcon'; import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon'; +import PullRequestIcon from '../../../components/icons-components/PullRequestIcon'; +import Tooltip from '../../../components/controls/Tooltip'; interface Props { task: Task; @@ -45,8 +52,10 @@ export default function TaskComponent({ task }: Props) { <td> {task.branchType === 'SHORT' && <ShortLivingBranchIcon className="little-spacer-right" />} {task.branchType === 'LONG' && <LongLivingBranchIcon className="little-spacer-right" />} + {task.pullRequest !== undefined && <PullRequestIcon className="little-spacer-right" />} {!task.branchType && + !task.pullRequest && task.componentQualifier && ( <span className="little-spacer-right"> <QualifierIcon qualifier={task.componentQualifier} /> @@ -56,7 +65,7 @@ export default function TaskComponent({ task }: Props) { {task.organization && <Organization organizationKey={task.organization} />} {task.componentName && ( - <Link className="spacer-right" to={getProjectUrl(task.componentKey, task.branch)}> + <Link className="spacer-right" to={getTaskComponentUrl(task.componentKey, task)}> {task.componentName} {task.branch && ( @@ -65,6 +74,15 @@ export default function TaskComponent({ task }: Props) { {task.branch} </span> )} + + {task.pullRequest && ( + <Tooltip overlay={task.pullRequestTitle}> + <span className="text-limited text-text-top"> + <span style={{ marginLeft: 5, marginRight: 5 }}>/</span> + {task.pullRequest} + </span> + </Tooltip> + )} </Link> )} @@ -72,3 +90,15 @@ export default function TaskComponent({ task }: Props) { </td> ); } + +function getTaskComponentUrl(componentKey: string, task: Task) { + if (task.branch && task.branchType === 'SHORT') { + return getShortLivingBranchUrl(componentKey, task.branchType); + } else if (task.branchType && task.branchType === 'LONG') { + return getLongLivingBranchUrl(componentKey, task.branchType); + } else if (task.pullRequest) { + return getPullRequestUrl(componentKey, task.pullRequest); + } else { + return getProjectUrl(componentKey); + } +} 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 93a63cdafd4..f71a484187b 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,7 +20,6 @@ exports[`renders 1`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "foo", }, } @@ -67,7 +66,6 @@ exports[`renders 3`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": "feature", "id": "foo", }, } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/types.ts b/server/sonar-web/src/main/js/apps/background-tasks/types.ts index b52783299ce..239c4581d61 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/types.ts +++ b/server/sonar-web/src/main/js/apps/background-tasks/types.ts @@ -29,6 +29,8 @@ export interface Task { hasScannerContext?: boolean; id: string; organization?: string; + pullRequest?: string; + pullRequestTitle?: string; startedAt?: string; status: string; submittedAt: string; diff --git a/server/sonar-web/src/main/js/apps/code/components/App.tsx b/server/sonar-web/src/main/js/apps/code/components/App.tsx index b6941e56388..937a66ee751 100644 --- a/server/sonar-web/src/main/js/apps/code/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/App.tsx @@ -26,16 +26,16 @@ import Search from './Search'; import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket'; import { Component as CodeComponent } from '../types'; import { retrieveComponentChildren, retrieveComponent, loadMoreChildren } from '../utils'; +import { Component, BranchLike } from '../../../app/types'; import ListFooter from '../../../components/controls/ListFooter'; import SourceViewer from '../../../components/SourceViewer/SourceViewer'; -import { parseError } from '../../../helpers/request'; -import { getBranchName } from '../../../helpers/branches'; +import { isSameBranchLike } from '../../../helpers/branches'; import { translate } from '../../../helpers/l10n'; -import { Component, Branch } from '../../../app/types'; +import { parseError } from '../../../helpers/request'; import '../code.css'; interface Props { - branch?: Branch; + branchLike?: BranchLike; component: Component; location: { query: { [x: string]: string } }; } @@ -67,7 +67,10 @@ export default class App extends React.PureComponent<Props, State> { } componentDidUpdate(prevProps: Props) { - if (prevProps.component !== this.props.component || prevProps.branch !== this.props.branch) { + if ( + prevProps.component !== this.props.component || + !isSameBranchLike(prevProps.branchLike, this.props.branchLike) + ) { this.handleComponentChange(); } else if (prevProps.location !== this.props.location) { this.handleUpdate(); @@ -80,14 +83,14 @@ export default class App extends React.PureComponent<Props, State> { } handleComponentChange() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; // we already know component's breadcrumbs, addComponentBreadcrumbs(component.key, component.breadcrumbs); this.setState({ loading: true }); const isPortfolio = ['VW', 'SVW'].includes(component.qualifier); - retrieveComponentChildren(component.key, isPortfolio, getBranchName(branch)) + retrieveComponentChildren(component.key, isPortfolio, branchLike) .then(() => { addComponent(component); if (this.mounted) { @@ -106,7 +109,7 @@ export default class App extends React.PureComponent<Props, State> { this.setState({ loading: true }); const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier); - retrieveComponent(componentKey, isPortfolio, getBranchName(this.props.branch)) + retrieveComponent(componentKey, isPortfolio, this.props.branchLike) .then(r => { if (this.mounted) { if (['FIL', 'UTS'].includes(r.component.qualifier)) { @@ -152,7 +155,7 @@ export default class App extends React.PureComponent<Props, State> { return; } const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier); - loadMoreChildren(baseComponent.key, page + 1, isPortfolio, getBranchName(this.props.branch)) + loadMoreChildren(baseComponent.key, page + 1, isPortfolio, this.props.branchLike) .then(r => { if (this.mounted) { this.setState({ @@ -177,7 +180,7 @@ export default class App extends React.PureComponent<Props, State> { }; render() { - const { branch, component, location } = this.props; + const { branchLike, component, location } = this.props; const { loading, error, @@ -187,8 +190,6 @@ export default class App extends React.PureComponent<Props, State> { total, sourceViewer } = this.state; - const branchName = getBranchName(branch); - const shouldShowBreadcrumbs = breadcrumbs.length > 1; const componentsClassName = classNames('boxed-group', 'boxed-group-inner', 'spacer-top', { @@ -202,7 +203,7 @@ export default class App extends React.PureComponent<Props, State> { {error && <div className="alert alert-danger">{error}</div>} <Search - branch={branchName} + branchLike={branchLike} component={component} location={location} onError={this.handleError} @@ -210,7 +211,11 @@ export default class App extends React.PureComponent<Props, State> { <div className="code-components"> {shouldShowBreadcrumbs && ( - <Breadcrumbs branch={branchName} breadcrumbs={breadcrumbs} rootComponent={component} /> + <Breadcrumbs + branchLike={branchLike} + breadcrumbs={breadcrumbs} + rootComponent={component} + /> )} {sourceViewer === undefined && @@ -218,7 +223,7 @@ export default class App extends React.PureComponent<Props, State> { <div className={componentsClassName}> <Components baseComponent={baseComponent} - branch={branchName} + branchLike={branchLike} components={components} rootComponent={component} /> @@ -232,7 +237,7 @@ export default class App extends React.PureComponent<Props, State> { {sourceViewer !== undefined && ( <div className="spacer-top"> - <SourceViewer branch={branchName} component={sourceViewer.key} /> + <SourceViewer branchLike={branchLike} component={sourceViewer.key} /> </div> )} </div> diff --git a/server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx b/server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx index 783b7126fa5..7ea212cd7ee 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx @@ -20,20 +20,21 @@ import * as React from 'react'; import ComponentName from './ComponentName'; import { Component } from '../types'; +import { BranchLike } from '../../../app/types'; interface Props { - branch?: string; + branchLike?: BranchLike; breadcrumbs: Component[]; rootComponent: Component; } -export default function Breadcrumbs({ branch, breadcrumbs, rootComponent }: Props) { +export default function Breadcrumbs({ branchLike, breadcrumbs, rootComponent }: Props) { return ( <ul className="code-breadcrumbs"> {breadcrumbs.map((component, index) => ( <li key={component.key}> <ComponentName - branch={branch} + branchLike={branchLike} canBrowse={index < breadcrumbs.length - 1} component={component} rootComponent={rootComponent} diff --git a/server/sonar-web/src/main/js/apps/code/components/Component.tsx b/server/sonar-web/src/main/js/apps/code/components/Component.tsx index 841d96baee7..df873439506 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Component.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Component.tsx @@ -24,12 +24,13 @@ import ComponentMeasure from './ComponentMeasure'; import ComponentLink from './ComponentLink'; import ComponentPin from './ComponentPin'; import { Component as IComponent } from '../types'; +import { BranchLike } from '../../../app/types'; const TOP_OFFSET = 200; const BOTTOM_OFFSET = 10; interface Props { - branch?: string; + branchLike?: BranchLike; canBrowse?: boolean; component: IComponent; previous?: IComponent; @@ -73,7 +74,7 @@ export default class Component extends React.PureComponent<Props> { render() { const { - branch, + branchLike, component, rootComponent, selected = false, @@ -89,10 +90,10 @@ export default class Component extends React.PureComponent<Props> { switch (component.qualifier) { case 'FIL': case 'UTS': - componentAction = <ComponentPin branch={branch} component={component} />; + componentAction = <ComponentPin branchLike={branchLike} component={component} />; break; default: - componentAction = <ComponentLink branch={branch} component={component} />; + componentAction = <ComponentLink branchLike={branchLike} component={component} />; } } @@ -121,7 +122,7 @@ export default class Component extends React.PureComponent<Props> { </td> <td className="code-name-cell"> <ComponentName - branch={branch} + branchLike={branchLike} component={component} rootComponent={rootComponent} previous={previous} diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx index 599dabdc412..60927947472 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx @@ -20,21 +20,22 @@ import * as React from 'react'; import { Link } from 'react-router'; import { Component } from '../types'; +import { BranchLike } from '../../../app/types'; import LinkIcon from '../../../components/icons-components/LinkIcon'; import { translate } from '../../../helpers/l10n'; -import { getProjectUrl } from '../../../helpers/urls'; +import { getBranchLikeUrl } from '../../../helpers/urls'; interface Props { - branch?: string; + branchLike?: BranchLike; component: Component; } -export default function ComponentLink({ component, branch }: Props) { +export default function ComponentLink({ component, branchLike }: Props) { return ( <Link className="link-no-underline" title={translate('code.open_component_page')} - to={getProjectUrl(component.refKey || component.key, branch)}> + to={getBranchLikeUrl(component.refKey || component.key, branchLike)}> <LinkIcon /> </Link> ); 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 7abb5e51a2d..0bd1290d662 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 @@ -20,9 +20,11 @@ import * as React from 'react'; import { Link } from 'react-router'; import Truncated from './Truncated'; +import { Component } from '../types'; import * as theme from '../../../app/theme'; +import { BranchLike } from '../../../app/types'; import QualifierIcon from '../../../components/shared/QualifierIcon'; -import { Component } from '../types'; +import { getBranchLikeQuery } from '../../../helpers/branches'; function getTooltip(component: Component) { const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS'; @@ -49,7 +51,7 @@ function mostCommitPrefix(strings: string[]) { } interface Props { - branch?: string; + branchLike?: BranchLike; canBrowse?: boolean; component: Component; previous?: Component; @@ -57,7 +59,7 @@ interface Props { } export default function ComponentName(props: Props) { - const { branch, component, rootComponent, previous, canBrowse = false } = props; + const { branchLike, component, rootComponent, previous, canBrowse = false } = props; const areBothDirs = component.qualifier === 'DIR' && previous && previous.qualifier === 'DIR'; const prefix = areBothDirs && previous !== undefined @@ -83,7 +85,7 @@ export default function ComponentName(props: Props) { </Link> ); } else if (canBrowse) { - const query = { id: rootComponent.key, branch }; + const query = { id: rootComponent.key, ...getBranchLikeQuery(branchLike) }; if (component.key !== rootComponent.key) { Object.assign(query, { selected: component.key }); } diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx index eb61f75da93..867f4bd476f 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx @@ -18,20 +18,21 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Workspace from '../../../components/workspace/main'; +import { Component } from '../types'; +import { BranchLike } from '../../../app/types'; import PinIcon from '../../../components/shared/pin-icon'; +import Workspace from '../../../components/workspace/main'; import { translate } from '../../../helpers/l10n'; -import { Component } from '../types'; interface Props { - branch?: string; + branchLike?: BranchLike; component: Component; } -export default function ComponentPin({ branch, component }: Props) { +export default function ComponentPin({ branchLike, component }: Props) { const handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); - Workspace.openComponent({ branch, key: component.key }); + Workspace.openComponent({ branchLike, key: component.key }); }; return ( diff --git a/server/sonar-web/src/main/js/apps/code/components/Components.tsx b/server/sonar-web/src/main/js/apps/code/components/Components.tsx index 25e99051994..98d3b569e71 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Components.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Components.tsx @@ -22,24 +22,25 @@ import Component from './Component'; import ComponentsEmpty from './ComponentsEmpty'; import ComponentsHeader from './ComponentsHeader'; import { Component as IComponent } from '../types'; +import { BranchLike } from '../../../app/types'; interface Props { baseComponent?: IComponent; - branch?: string; + branchLike?: BranchLike; components: IComponent[]; rootComponent: IComponent; selected?: IComponent; } export default function Components(props: Props) { - const { baseComponent, branch, components, rootComponent, selected } = props; + const { baseComponent, branchLike, components, rootComponent, selected } = props; return ( <table className="data zebra"> <ComponentsHeader baseComponent={baseComponent} rootComponent={rootComponent} /> {baseComponent && ( <tbody> <Component - branch={branch} + branchLike={branchLike} component={baseComponent} key={baseComponent.key} rootComponent={rootComponent} @@ -53,7 +54,7 @@ export default function Components(props: Props) { {components.length ? ( components.map((component, index, list) => ( <Component - branch={branch} + branchLike={branchLike} canBrowse={true} component={component} key={component.key} diff --git a/server/sonar-web/src/main/js/apps/code/components/Search.tsx b/server/sonar-web/src/main/js/apps/code/components/Search.tsx index 4a5536ed3de..abeeacc511a 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Search.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Search.tsx @@ -21,15 +21,17 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import * as classNames from 'classnames'; import Components from './Components'; -import { getTree } from '../../../api/components'; -import { parseError } from '../../../helpers/request'; -import { getProjectUrl } from '../../../helpers/urls'; import { Component } from '../types'; +import { getTree } from '../../../api/components'; +import { BranchLike } from '../../../app/types'; import SearchBox from '../../../components/controls/SearchBox'; +import { getBranchLikeQuery } from '../../../helpers/branches'; import { translate } from '../../../helpers/l10n'; +import { parseError } from '../../../helpers/request'; +import { getProjectUrl } from '../../../helpers/urls'; interface Props { - branch?: string; + branchLike?: BranchLike; component: Component; location: {}; onError: (error: string) => void; @@ -89,7 +91,7 @@ export default class Search extends React.PureComponent<Props, State> { } handleSelectCurrent() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; const { results, selectedIndex } = this.state; if (results != null && selectedIndex != null) { const selected = results[selectedIndex]; @@ -99,7 +101,7 @@ export default class Search extends React.PureComponent<Props, State> { } else { this.context.router.push({ pathname: '/code', - query: { branch, id: component.key, selected: selected.key } + query: { id: component.key, selected: selected.key, ...getBranchLikeQuery(branchLike) } }); } } @@ -125,13 +127,18 @@ export default class Search extends React.PureComponent<Props, State> { handleSearch = (query: string) => { if (this.mounted) { - const { branch, component, onError } = this.props; + const { branchLike, component, onError } = this.props; this.setState({ loading: true }); const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier); const qualifiers = isPortfolio ? 'SVW,TRK' : 'BRC,UTS,FIL'; - getTree(component.key, { branch, q: query, s: 'qualifier,name', qualifiers }) + getTree(component.key, { + q: query, + s: 'qualifier,name', + qualifiers, + ...getBranchLikeQuery(branchLike) + }) .then(r => { if (this.mounted) { this.setState({ @@ -184,7 +191,7 @@ export default class Search extends React.PureComponent<Props, State> { {results != null && ( <div className="boxed-group boxed-group-inner spacer-top"> <Components - branch={this.props.branch} + branchLike={this.props.branchLike} components={results} rootComponent={component} selected={selected} diff --git a/server/sonar-web/src/main/js/apps/code/utils.ts b/server/sonar-web/src/main/js/apps/code/utils.ts index 80fdbadea8c..75ef812c977 100644 --- a/server/sonar-web/src/main/js/apps/code/utils.ts +++ b/server/sonar-web/src/main/js/apps/code/utils.ts @@ -28,6 +28,8 @@ import { } from './bucket'; import { Breadcrumb, Component } from './types'; import { getChildren, getComponent, getBreadcrumbs } from '../../api/components'; +import { BranchLike } from '../../app/types'; +import { getBranchLikeQuery } from '../../helpers/branches'; const METRICS = [ 'ncloc', @@ -54,11 +56,15 @@ function requestChildren( componentKey: string, metrics: string[], page: number, - branch?: string + branchLike?: BranchLike ): Promise<Component[]> { - return getChildren(componentKey, metrics, { branch, p: page, ps: PAGE_SIZE }).then(r => { + return getChildren(componentKey, metrics, { + p: page, + ps: PAGE_SIZE, + ...getBranchLikeQuery(branchLike) + }).then(r => { if (r.paging.total > r.paging.pageSize * r.paging.pageIndex) { - return requestChildren(componentKey, metrics, page + 1, branch).then(moreComponents => { + return requestChildren(componentKey, metrics, page + 1, branchLike).then(moreComponents => { return [...r.components, ...moreComponents]; }); } @@ -69,9 +75,9 @@ function requestChildren( function requestAllChildren( componentKey: string, metrics: string[], - branch?: string + branchLike?: BranchLike ): Promise<Component[]> { - return requestChildren(componentKey, metrics, 1, branch); + return requestChildren(componentKey, metrics, 1, branchLike); } interface Children { @@ -84,13 +90,13 @@ interface ExpandRootDirFunc { (children: Children): Promise<Children>; } -function expandRootDir(metrics: string[], branch?: string): ExpandRootDirFunc { +function expandRootDir(metrics: string[], branchLike?: BranchLike): ExpandRootDirFunc { return function({ components, total, ...other }) { const rootDir = components.find( (component: Component) => component.qualifier === 'DIR' && component.name === '/' ); if (rootDir) { - return requestAllChildren(rootDir.key, metrics, branch).then(rootDirComponents => { + return requestAllChildren(rootDir.key, metrics, branchLike).then(rootDirComponents => { const nextComponents = without([...rootDirComponents, ...components], rootDir); const nextTotal = total + rootDirComponents.length - /* root dir */ 1; return { components: nextComponents, total: nextTotal, ...other }; @@ -133,7 +139,11 @@ function getMetrics(isPortfolio: boolean) { return isPortfolio ? PORTFOLIO_METRICS : METRICS; } -function retrieveComponentBase(componentKey: string, isPortfolio: boolean, branch?: string) { +function retrieveComponentBase( + componentKey: string, + isPortfolio: boolean, + branchLike?: BranchLike +) { const existing = getComponentFromBucket(componentKey); if (existing) { return Promise.resolve(existing); @@ -141,7 +151,11 @@ function retrieveComponentBase(componentKey: string, isPortfolio: boolean, branc const metrics = getMetrics(isPortfolio); - return getComponent(componentKey, metrics, branch).then(component => { + return getComponent({ + componentKey, + metricKeys: metrics.join(), + ...getBranchLikeQuery(branchLike) + }).then(component => { addComponent(component); return component; }); @@ -150,7 +164,7 @@ function retrieveComponentBase(componentKey: string, isPortfolio: boolean, branc export function retrieveComponentChildren( componentKey: string, isPortfolio: boolean, - branch?: string + branchLike?: BranchLike ): Promise<{ components: Component[]; page: number; total: number }> { const existing = getComponentChildren(componentKey); if (existing) { @@ -163,9 +177,13 @@ export function retrieveComponentChildren( const metrics = getMetrics(isPortfolio); - return getChildren(componentKey, metrics, { branch, ps: PAGE_SIZE, s: 'qualifier,name' }) + return getChildren(componentKey, metrics, { + ps: PAGE_SIZE, + s: 'qualifier,name', + ...getBranchLikeQuery(branchLike) + }) .then(prepareChildren) - .then(expandRootDir(metrics, branch)) + .then(expandRootDir(metrics, branchLike)) .then(r => { addComponentChildren(componentKey, r.components, r.total, r.page); storeChildrenBase(r.components); @@ -175,18 +193,18 @@ export function retrieveComponentChildren( } function retrieveComponentBreadcrumbs( - componentKey: string, - branch?: string + component: string, + branchLike?: BranchLike ): Promise<Breadcrumb[]> { - const existing = getComponentBreadcrumbs(componentKey); + const existing = getComponentBreadcrumbs(component); if (existing) { return Promise.resolve(existing); } - return getBreadcrumbs(componentKey, branch) + return getBreadcrumbs({ component, ...getBranchLikeQuery(branchLike) }) .then(skipRootDir) .then(breadcrumbs => { - addComponentBreadcrumbs(componentKey, breadcrumbs); + addComponentBreadcrumbs(component, breadcrumbs); return breadcrumbs; }); } @@ -194,7 +212,7 @@ function retrieveComponentBreadcrumbs( export function retrieveComponent( componentKey: string, isPortfolio: boolean, - branch?: string + branchLike?: BranchLike ): Promise<{ breadcrumbs: Component[]; component: Component; @@ -203,9 +221,9 @@ export function retrieveComponent( total: number; }> { return Promise.all([ - retrieveComponentBase(componentKey, isPortfolio, branch), - retrieveComponentChildren(componentKey, isPortfolio, branch), - retrieveComponentBreadcrumbs(componentKey, branch) + retrieveComponentBase(componentKey, isPortfolio, branchLike), + retrieveComponentChildren(componentKey, isPortfolio, branchLike), + retrieveComponentBreadcrumbs(componentKey, branchLike) ]).then(r => { return { component: r[0], @@ -221,13 +239,17 @@ export function loadMoreChildren( componentKey: string, page: number, isPortfolio: boolean, - branch?: string + branchLike?: BranchLike ): Promise<Children> { const metrics = getMetrics(isPortfolio); - return getChildren(componentKey, metrics, { branch, ps: PAGE_SIZE, p: page }) + return getChildren(componentKey, metrics, { + ps: PAGE_SIZE, + p: page, + ...getBranchLikeQuery(branchLike) + }) .then(prepareChildren) - .then(expandRootDir(metrics, branch)) + .then(expandRootDir(metrics, branchLike)) .then(r => { addComponentChildren(componentKey, r.components, r.total, r.page); storeChildrenBase(r.components); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/App.js b/server/sonar-web/src/main/js/apps/component-measures/components/App.js index 4198c6689fd..d357ff4c15a 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/App.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/App.js @@ -26,7 +26,7 @@ import MeasureOverviewContainer from './MeasureOverviewContainer'; import Sidebar from '../sidebar/Sidebar'; import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; import { hasBubbleChart, parseQuery, serializeQuery } from '../utils'; -import { getBranchName } from '../../../helpers/branches'; +import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; import { translate } from '../../../helpers/l10n'; import { getDisplayMetrics } from '../../../helpers/measures'; /*:: import type { Component, Query, Period } from '../types'; */ @@ -36,14 +36,14 @@ import { getDisplayMetrics } from '../../../helpers/measures'; import '../style.css'; /*:: type Props = {| - branch?: {}, + branchLike?: { id?: string; name: string }, component: Component, currentUser: { isLoggedIn: boolean }, location: { pathname: string, query: RawQuery }, fetchMeasures: ( component: string, metricsKey: Array<string>, - branch?: string + branchLike?: { id?: string; name: string } ) => Promise<{ component: Component, measures: Array<MeasureEnhanced>, leakPeriod: ?Period }>, fetchMetrics: () => void, metrics: { [string]: Metric }, @@ -88,7 +88,7 @@ export default class App extends React.PureComponent { componentWillReceiveProps(nextProps /*: Props */) { if ( - nextProps.branch !== this.props.branch || + !isSameBranchLike(nextProps.branchLike, this.props.branchLike) || nextProps.component.key !== this.props.component.key || nextProps.metrics !== this.props.metrics ) { @@ -107,10 +107,10 @@ export default class App extends React.PureComponent { } } - fetchMeasures = ({ branch, component, fetchMeasures, metrics } /*: Props */) => { + fetchMeasures = ({ branchLike, component, fetchMeasures, metrics } /*: Props */) => { this.setState({ loading: true }); const filteredKeys = getDisplayMetrics(Object.values(metrics)).map(metric => metric.key); - fetchMeasures(component.key, filteredKeys, getBranchName(branch)).then( + fetchMeasures(component.key, filteredKeys, branchLike).then( ({ measures, leakPeriod }) => { if (this.mounted) { this.setState({ @@ -137,7 +137,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...query, - branch: getBranchName(this.props.branch), + ...getBranchLikeQuery(this.props.branchLike), id: this.props.component.key } }); @@ -148,7 +148,7 @@ export default class App extends React.PureComponent { if (isLoading) { return <i className="spinner spinner-margin" />; } - const { branch, component, fetchMeasures, metrics } = this.props; + const { branchLike, component, fetchMeasures, metrics } = this.props; const { leakPeriod } = this.state; const query = parseQuery(this.props.location.query); const metric = metrics[query.metric]; @@ -174,7 +174,7 @@ export default class App extends React.PureComponent { {metric != null && ( <MeasureContentContainer - branch={getBranchName(branch)} + branchLike={branchLike} className="layout-page-main" currentUser={this.props.currentUser} rootComponent={component} @@ -191,7 +191,7 @@ export default class App extends React.PureComponent { {metric == null && hasBubbleChart(query.metric) && ( <MeasureOverviewContainer - branch={getBranchName(branch)} + branchLike={branchLike} className="layout-page-main" rootComponent={component} currentUser={this.props.currentUser} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js index a9122702894..54d63a4dddd 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js @@ -27,6 +27,7 @@ import { fetchMetrics } from '../../../store/rootActions'; import { getMeasuresAndMeta } from '../../../api/measures'; import { getLeakPeriod } from '../../../helpers/periods'; import { enhanceMeasure } from '../../../components/measure/utils'; +import { getBranchLikeQuery } from '../../../helpers/branches'; /*:: import type { Component, Period } from '../types'; */ /*:: import type { Measure, MeasureEnhanced } from '../../../components/measure/types'; */ @@ -50,7 +51,7 @@ function banQualityGate(component /*: Component */) /*: Array<Measure> */ { const fetchMeasures = ( component /*: string */, metricsKey /*: Array<string> */, - branch /*: string | void */ + branchLike /*: { id?: string; name: string } | void */ ) => (dispatch, getState) => { if (metricsKey.length <= 0) { return Promise.resolve({ component: {}, measures: [], leakPeriod: null }); @@ -58,7 +59,7 @@ const fetchMeasures = ( return getMeasuresAndMeta(component, metricsKey, { additionalFields: 'periods', - branch + ...getBranchLikeQuery(branchLike) }).then(r => { const measures = banQualityGate(r.component).map(measure => enhanceMeasure(measure, getMetrics(getState())) diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js index fedd4d52dfe..7709a31d257 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js @@ -22,11 +22,12 @@ import React from 'react'; import key from 'keymaster'; import Breadcrumb from './Breadcrumb'; import { getBreadcrumbs } from '../../../api/components'; +import { getBranchLikeQuery } from '../../../helpers/branches'; /*:: import type { Component } from '../types'; */ /*:: type Props = {| backToFirst: boolean, - branch?: string, + branchLike?: { id?: string, name: string }, className?: string, component: Component, handleSelect: string => void, @@ -76,7 +77,7 @@ export default class Breadcrumbs extends React.PureComponent { key.unbind('left', 'measures-files'); } - fetchBreadcrumbs = ({ branch, component, rootComponent } /*: Props */) => { + fetchBreadcrumbs = ({ branchLike, component, rootComponent } /*: Props */) => { const isRoot = component.key === rootComponent.key; if (isRoot) { if (this.mounted) { @@ -84,11 +85,13 @@ export default class Breadcrumbs extends React.PureComponent { } return; } - getBreadcrumbs(component.key, branch).then(breadcrumbs => { - if (this.mounted) { - this.setState({ breadcrumbs }); + getBreadcrumbs({ component: component.key, ...getBranchLikeQuery(branchLike) }).then( + breadcrumbs => { + if (this.mounted) { + this.setState({ breadcrumbs }); + } } - }); + ); }; render() { diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js index a9d9f81c35d..8feb83e1167 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js @@ -34,6 +34,7 @@ import { complementary } from '../config/complementary'; import { enhanceComponent, isFileType, isViewType } from '../utils'; import { getProjectUrl } from '../../../helpers/urls'; import { isDiffMetric } from '../../../helpers/measures'; +import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; /*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */ /*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ /*:: import type { Metric } from '../../../store/metrics/actions'; */ @@ -42,7 +43,7 @@ import { isDiffMetric } from '../../../helpers/measures'; // https://github.com/facebook/flow/issues/3147 // router: { push: ({ pathname: string, query?: RawQuery }) => void } /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, className?: string, component: Component, currentUser: { isLoggedIn: boolean }, @@ -87,7 +88,7 @@ export default class MeasureContent extends React.PureComponent { componentWillReceiveProps(nextProps /*: Props */) { if ( - nextProps.branch !== this.props.branch || + !isSameBranchLike(nextProps.branchLike, this.props.branchLike) || nextProps.component !== this.props.component || nextProps.metric !== this.props.metric ) { @@ -115,7 +116,7 @@ export default class MeasureContent extends React.PureComponent { const strategy = view === 'list' ? 'leaves' : 'children'; const metricKeys = [metric.key]; const opts /*: Object */ = { - branch: this.props.branch, + ...getBranchLikeQuery(this.props.branchLike), metricSortFilter: 'withMeasuresOnly' }; const isDiff = isDiffMetric(metric.key); @@ -225,7 +226,7 @@ export default class MeasureContent extends React.PureComponent { return ( <div className="measure-details-viewer"> <CodeView - branch={this.props.branch} + branchLike={this.props.branchLike} component={this.props.component} components={this.state.components} leakPeriod={this.props.leakPeriod} @@ -244,7 +245,7 @@ export default class MeasureContent extends React.PureComponent { const selectedIdx = this.getSelectedIndex(); return ( <FilesView - branch={this.props.branch} + branchLike={this.props.branchLike} components={this.state.components} fetchMore={this.fetchMoreComponents} handleOpen={this.onOpenComponent} @@ -261,7 +262,7 @@ export default class MeasureContent extends React.PureComponent { if (view === 'treemap') { return ( <TreeMapView - branch={this.props.branch} + branchLike={this.props.branchLike} components={this.state.components} handleSelect={this.onOpenComponent} metric={metric} @@ -274,7 +275,7 @@ export default class MeasureContent extends React.PureComponent { } render() { - const { branch, component, currentUser, measure, metric, rootComponent, view } = this.props; + const { branchLike, component, currentUser, measure, metric, rootComponent, view } = this.props; const isLoggedIn = currentUser && currentUser.isLoggedIn; const isFile = isFileType(component); const selectedIdx = this.getSelectedIndex(); @@ -288,7 +289,7 @@ export default class MeasureContent extends React.PureComponent { <div className="layout-page-main-inner"> <Breadcrumbs backToFirst={view === 'list'} - branch={branch} + branchLike={branchLike} className="measure-breadcrumbs spacer-right text-ellipsis" component={component} handleSelect={this.onOpenComponent} @@ -327,7 +328,7 @@ export default class MeasureContent extends React.PureComponent { measure != null && ( <div className="layout-page-main-inner measure-details-content"> <MeasureHeader - branch={branch} + branchLike={branchLike} component={component} components={this.state.components} leakPeriod={this.props.leakPeriod} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js index beaca1bd6e1..1998460996e 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js @@ -26,14 +26,14 @@ import MeasureContent from './MeasureContent'; /*:: import type { RawQuery } from '../../../helpers/query'; */ /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, className?: string, currentUser: { isLoggedIn: boolean }, rootComponent: Component, fetchMeasures: ( component: string, metricsKey: Array<string>, - branch?: string + branchLike?: { id?: string; name: string } ) => Promise<{ component: Component, measures: Array<MeasureEnhanced> }>, leakPeriod?: Period, metric: Metric, @@ -89,7 +89,7 @@ export default class MeasureContentContainer extends React.PureComponent { this.mounted = false; } - fetchMeasure = ({ branch, rootComponent, fetchMeasures, metric, selected } /*: Props */) => { + fetchMeasure = ({ branchLike, rootComponent, fetchMeasures, metric, selected } /*: Props */) => { this.updateLoading({ measure: true }); const metricKeys = [metric.key]; @@ -101,7 +101,7 @@ export default class MeasureContentContainer extends React.PureComponent { metricKeys.push('file_complexity_distribution'); } - fetchMeasures(selected || rootComponent.key, metricKeys, branch).then( + fetchMeasures(selected || rootComponent.key, metricKeys, branchLike).then( ({ component, measures }) => { if (this.mounted) { const measure = measures.find(measure => measure.metric.key === metric.key); @@ -134,7 +134,7 @@ export default class MeasureContentContainer extends React.PureComponent { return ( <MeasureContent - branch={this.props.branch} + branchLike={this.props.branchLike} className={this.props.className} component={this.state.component} currentUser={this.props.currentUser} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js index 1690866ffde..f1aac5309b1 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js @@ -57,7 +57,7 @@ class MeasureFavoriteContainer extends React.PureComponent { } fetchComponentFavorite({ component, onReceiveComponent } /*: Props */) { - getComponentForSourceViewer(component).then(component => { + getComponentForSourceViewer({ component }).then(component => { this.setState({ component }); onReceiveComponent(component); }); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js index 87c543aaa76..5c809374492 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js @@ -34,7 +34,7 @@ import { isDiffMetric } from '../../../helpers/measures'; /*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, component: Component, components: Array<Component>, leakPeriod?: Period, @@ -43,7 +43,7 @@ import { isDiffMetric } from '../../../helpers/measures'; |}; */ export default function MeasureHeader(props /*: Props*/) { - const { branch, component, leakPeriod, measure, secondaryMeasure } = props; + const { branchLike, component, leakPeriod, measure, secondaryMeasure } = props; const { metric } = measure; const isDiff = isDiffMetric(metric.key); return ( @@ -72,7 +72,7 @@ export default function MeasureHeader(props /*: Props*/) { overlay={translate('component_measures.show_metric_history')}> <Link className="js-show-history spacer-left button button-small" - to={getMeasureHistoryUrl(component.key, metric.key, branch)}> + to={getMeasureHistoryUrl(component.key, metric.key, branchLike)}> <HistoryIcon /> </Link> </Tooltip> diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js index a5e79b7a134..0128f7b8b1f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js @@ -27,11 +27,12 @@ import BubbleChart from '../drilldown/BubbleChart'; import SourceViewer from '../../../components/SourceViewer/SourceViewer'; import { getComponentLeaves } from '../../../api/components'; import { enhanceComponent, getBubbleMetrics, isFileType } from '../utils'; +import { getBranchLikeQuery } from '../../../helpers/branches'; /*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */ /*:: import type { Metric } from '../../../store/metrics/actions'; */ /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, className?: string, component: Component, currentUser: { isLoggedIn: boolean }, @@ -79,9 +80,10 @@ export default class MeasureOverview extends React.PureComponent { } fetchComponents = (props /*: Props */) => { - const { branch, component, domain, metrics } = props; + const { branchLike, component, domain, metrics } = props; if (isFileType(component)) { - return this.setState({ components: [], paging: null }); + this.setState({ components: [], paging: null }); + return; } const { x, y, size, colors } = getBubbleMetrics(domain, metrics); const metricsKey = [x.key, y.key, size.key]; @@ -89,7 +91,7 @@ export default class MeasureOverview extends React.PureComponent { metricsKey.push(colors.map(metric => metric.key)); } const options = { - branch, + ...getBranchLikeQuery(branchLike), s: 'metric', metricSort: size.key, asc: false, @@ -114,11 +116,11 @@ export default class MeasureOverview extends React.PureComponent { }; renderContent() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; if (isFileType(component)) { return ( <div className="measure-details-viewer"> - <SourceViewer branch={branch} component={component.key} /> + <SourceViewer branchLike={branchLike} component={component.key} /> </div> ); } @@ -135,7 +137,7 @@ export default class MeasureOverview extends React.PureComponent { } render() { - const { branch, component, currentUser, leakPeriod, rootComponent } = this.props; + const { branchLike, component, currentUser, leakPeriod, rootComponent } = this.props; const isLoggedIn = currentUser && currentUser.isLoggedIn; const isFile = isFileType(component); return ( @@ -145,7 +147,7 @@ export default class MeasureOverview extends React.PureComponent { <div className="layout-page-main-inner"> <Breadcrumbs backToFirst={true} - branch={branch} + branchLike={branchLike} className="measure-breadcrumbs spacer-right text-ellipsis" component={component} handleSelect={this.props.updateSelected} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js index 7268b08f308..b69a9a8378f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js @@ -23,12 +23,13 @@ import MeasureOverview from './MeasureOverview'; import { getComponentShow } from '../../../api/components'; import { getProjectUrl } from '../../../helpers/urls'; import { isViewType } from '../utils'; +import { getBranchLikeQuery } from '../../../helpers/branches'; /*:: import type { Component, Period, Query } from '../types'; */ /*:: import type { RawQuery } from '../../../helpers/query'; */ /*:: import type { Metric } from '../../../store/metrics/actions'; */ /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, className?: string, rootComponent: Component, currentUser: { isLoggedIn: boolean }, @@ -81,14 +82,14 @@ export default class MeasureOverviewContainer extends React.PureComponent { this.mounted = false; } - fetchComponent = ({ branch, rootComponent, selected } /*: Props */) => { + fetchComponent = ({ branchLike, rootComponent, selected } /*: Props */) => { if (!selected || rootComponent.key === selected) { this.setState({ component: rootComponent }); this.updateLoading({ component: false }); return; } this.updateLoading({ component: true }); - getComponentShow(selected, branch).then( + getComponentShow({ component: selected, ...getBranchLikeQuery(branchLike) }).then( ({ component }) => { if (this.mounted) { this.setState({ component }); @@ -122,7 +123,7 @@ export default class MeasureOverviewContainer extends React.PureComponent { return ( <MeasureOverview - branch={this.props.branch} + branchLike={this.props.branchLike} className={this.props.className} component={this.state.component} currentUser={this.props.currentUser} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js index dfa9a644f24..5b02e3d87fb 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js @@ -77,7 +77,10 @@ it('should render correctly for leak', () => { }); it('should render with branch', () => { - expect(shallow(<MeasureHeader branch="feature" {...PROPS} />).find('Link')).toMatchSnapshot(); + const shortBranch = { isMain: false, name: 'feature', mergeBranch: '', type: 'SHORT' }; + expect( + shallow(<MeasureHeader branchLike={shortBranch} {...PROPS} />).find('Link') + ).toMatchSnapshot(); }); it('should display secondary measure too', () => { diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap index d6c9bceb55a..8da497107c3 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap @@ -86,7 +86,6 @@ exports[`should render correctly 1`] = ` Object { "pathname": "/project/activity", "query": Object { - "branch": undefined, "custom_metrics": "reliability_rating", "graph": "custom", "id": "foo", diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js index 5270c221842..1c0747851f1 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js @@ -25,7 +25,7 @@ import SourceViewer from '../../../components/SourceViewer/SourceViewer'; /*:: import type { Metric } from '../../../store/metrics/actions'; */ /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, component: ComponentEnhanced, components: Array<ComponentEnhanced>, leakPeriod?: Period, @@ -81,7 +81,7 @@ export default class CodeView extends React.PureComponent { }; render() { - const { branch, component } = this.props; - return <SourceViewer branch={branch} component={component.key} />; + const { branchLike, component } = this.props; + return <SourceViewer branchLike={branchLike} component={component.key} />; } } 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 033a8bcf66b..248b6469e2b 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 @@ -23,11 +23,11 @@ import { Link } from 'react-router'; import LinkIcon from '../../../components/icons-components/LinkIcon'; import QualifierIcon from '../../../components/icons-components/QualifierIcon'; import { splitPath } from '../../../helpers/path'; -import { getPathUrlAsString, getProjectUrl } from '../../../helpers/urls'; +import { getPathUrlAsString, getBranchLikeUrl } from '../../../helpers/urls'; /*:: import type { ComponentEnhanced } from '../types'; */ /*:: type Props = { - branch?: string, + branchLike?: { id?: string; name: string }, component: ComponentEnhanced, onClick: string => void }; */ @@ -65,15 +65,16 @@ export default class ComponentCell extends React.PureComponent { } render() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; return ( <td className="measure-details-component-cell"> <div className="text-ellipsis"> + {/* TODO make this <a> link a react-router <Link /> */} {component.refKey == null ? ( <a id={'component-measures-component-link-' + component.key} className="link-no-underline" - href={getPathUrlAsString(getProjectUrl(component.key, branch))} + href={getPathUrlAsString(getBranchLikeUrl(component.key, branchLike))} onClick={this.handleClick}> {this.renderInner()} </a> @@ -81,7 +82,7 @@ export default class ComponentCell extends React.PureComponent { <Link className="link-no-underline" id={'component-measures-component-link-' + component.key} - to={getProjectUrl(component.refKey, branch)}> + to={getBranchLikeUrl(component.refKey, branchLike)}> <span className="big-spacer-right"> <LinkIcon /> </span> diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js index f2b45b73878..0e29a704f0c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js @@ -27,7 +27,7 @@ import { getLocalizedMetricName } from '../../../helpers/l10n'; /*:: import type { Metric } from '../../../store/metrics/actions'; */ /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, components: Array<ComponentEnhanced>, onClick: string => void, metric: Metric, @@ -36,7 +36,7 @@ import { getLocalizedMetricName } from '../../../helpers/l10n'; |}; */ export default function ComponentsList( - { branch, components, onClick, metrics, metric, selectedComponent } /*: Props */ + { branchLike, components, onClick, metrics, metric, selectedComponent } /*: Props */ ) { if (!components.length) { return <EmptyResult />; @@ -65,7 +65,7 @@ export default function ComponentsList( {components.map(component => ( <ComponentsListRow key={component.id} - branch={branch} + branchLike={branchLike} component={component} otherMetrics={otherMetrics} isSelected={component.key === selectedComponent} diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js index e87b224967f..47a5d2bf29a 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js @@ -26,7 +26,7 @@ import MeasureCell from './MeasureCell'; /*:: import type { Metric } from '../../../store/metrics/actions'; */ /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, component: ComponentEnhanced, isSelected: boolean, onClick: string => void, @@ -35,7 +35,7 @@ import MeasureCell from './MeasureCell'; |}; */ export default function ComponentsListRow(props /*: Props */) { - const { branch, component } = props; + const { branchLike, component } = props; const otherMeasures = props.otherMetrics.map(metric => { const measure = component.measures.find(measure => measure.metric.key === metric.key); return { ...measure, metric }; @@ -45,7 +45,7 @@ export default function ComponentsListRow(props /*: Props */) { }); return ( <tr className={rowClass}> - <ComponentCell branch={branch} component={component} onClick={props.onClick} /> + <ComponentCell branchLike={branchLike} component={component} onClick={props.onClick} /> <MeasureCell component={component} metric={props.metric} /> diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js index 9a0ffde8c05..2fd8d7feb27 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js @@ -28,7 +28,7 @@ import { scrollToElement } from '../../../helpers/scrolling'; /*:: import type { Metric } from '../../../store/metrics/actions'; */ /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, components: Array<ComponentEnhanced>, fetchMore: () => void, handleSelect: string => void, @@ -123,7 +123,7 @@ export default class ListView extends React.PureComponent { return ( <div ref={elem => (this.listContainer = elem)}> <ComponentsList - branch={this.props.branch} + branchLike={this.props.branchLike} components={this.props.components} metrics={this.props.metrics} metric={this.props.metric} diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js index 3892abf2689..1dd3fd68f26 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js @@ -29,13 +29,13 @@ import QualifierIcon from '../../../components/icons-components/QualifierIcon'; import TreeMap from '../../../components/charts/TreeMap'; import { translate, translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n'; import { formatMeasure, isDiffMetric } from '../../../helpers/measures'; -import { getProjectUrl } from '../../../helpers/urls'; +import { getBranchLikeUrl } from '../../../helpers/urls'; /*:: import type { Metric } from '../../../store/metrics/actions'; */ /*:: import type { ComponentEnhanced } from '../types'; */ /*:: import type { TreeMapItem } from '../../../components/charts/TreeMap'; */ /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, components: Array<ComponentEnhanced>, handleSelect: string => void, metric: Metric @@ -64,7 +64,7 @@ export default class TreeMapView extends React.PureComponent { } } - getTreemapComponents = ({ branch, components, metric } /*: Props */) => { + getTreemapComponents = ({ branchLike, components, metric } /*: Props */) => { const colorScale = this.getColorScale(metric); return components .map(component => { @@ -95,7 +95,7 @@ export default class TreeMapView extends React.PureComponent { sizeValue ), label: component.name, - link: getProjectUrl(component.refKey || component.key, branch) + link: getBranchLikeUrl(component.refKey || component.key, branchLike) }; }) .filter(Boolean); 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 96ff380b906..71fc859ea2f 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,6 +18,7 @@ * 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'; interface Props { @@ -26,6 +27,7 @@ interface Props { branch?: string; id: string; line?: string; + pullRequest?: string; }; }; } @@ -45,15 +47,30 @@ export default class App extends React.PureComponent<Props> { }; render() { - const { branch, id, line } = this.props.location.query; + const { branch, id, line, pullRequest } = this.props.location.query; const finalLine = line ? Number(line) : undefined; + // 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; + } + return ( <div className="page page-limited"> <SourceViewer aroundLine={finalLine} - branch={branch} + branchLike={fakeBranchLike} component={id} highlightedLine={finalLine} onLoaded={this.scrollToLine} diff --git a/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap index 1cb5c653533..f5dd7e1a6ce 100644 --- a/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap @@ -6,7 +6,14 @@ exports[`renders 1`] = ` > <Connect(SourceViewerBase) aroundLine={7} - branch="b" + branchLike={ + Object { + "isMain": false, + "mergeBranch": "", + "name": "b", + "type": "SHORT", + } + } component="foo" highlightedLine={7} onLoaded={[Function]} diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.d.ts b/server/sonar-web/src/main/js/apps/issues/components/App.d.ts index 9bafb845242..211061e58bd 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.d.ts +++ b/server/sonar-web/src/main/js/apps/issues/components/App.d.ts @@ -18,11 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { Component, CurrentUser } from '../../../app/types'; +import { Component, CurrentUser, BranchLike } from '../../../app/types'; import { RawQuery } from '../../../helpers/query'; interface Props { - branch?: { name: string }; + branchLike?: BranchLike; component?: Component; currentUser: CurrentUser; fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<any>; diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js index bc6cf9826ff..85bf0fa458e 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.js +++ b/server/sonar-web/src/main/js/apps/issues/components/App.js @@ -59,7 +59,12 @@ import ListFooter from '../../../components/controls/ListFooter'; import EmptySearch from '../../../components/common/EmptySearch'; import FiltersHeader from '../../../components/common/FiltersHeader'; import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; -import { getBranchName, isShortLivingBranch } from '../../../helpers/branches'; +import { + isShortLivingBranch, + isSameBranchLike, + getBranchLikeQuery, + isPullRequest +} from '../../../helpers/branches'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { scrollToElement } from '../../../helpers/scrolling'; import Checkbox from '../../../components/controls/Checkbox'; @@ -69,7 +74,7 @@ import '../styles.css'; /*:: export type Props = { - branch?: { name: string }, + branchLike?: { id?: string; name: string }, component?: Component, currentUser: CurrentUser, fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<*>, @@ -193,7 +198,7 @@ export default class App extends React.PureComponent { const { query: prevQuery } = prevProps.location; if ( prevProps.component !== this.props.component || - prevProps.branch !== this.props.branch || + !isSameBranchLike(prevProps.branchLike, this.props.branchLike) || !areQueriesEqual(prevQuery, query) || areMyIssuesSelected(prevQuery) !== areMyIssuesSelected(query) ) { @@ -337,7 +342,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...serializeQuery(this.state.query), - branch: getBranchName(this.props.branch), + ...getBranchLikeQuery(this.props.branchLike), id: this.props.component && this.props.component.key, myIssues: this.state.myIssues ? 'true' : undefined, open: issue @@ -356,7 +361,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...serializeQuery(this.state.query), - branch: getBranchName(this.props.branch), + ...getBranchLikeQuery(this.props.branchLike), id: this.props.component && this.props.component.key, myIssues: this.state.myIssues ? 'true' : undefined, open: undefined @@ -399,7 +404,7 @@ export default class App extends React.PureComponent { : undefined; const parameters = { - branch: getBranchName(this.props.branch), + ...getBranchLikeQuery(this.props.branchLike), componentKeys: component && component.key, s: 'FILE_LINE', ...serializeQuery(query), @@ -594,7 +599,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...serializeQuery({ ...this.state.query, ...changes }), - branch: getBranchName(this.props.branch), + ...getBranchLikeQuery(this.props.branchLike), id: this.props.component && this.props.component.key, myIssues: this.state.myIssues ? 'true' : undefined } @@ -610,7 +615,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...serializeQuery({ ...this.state.query, assigned: true, assignees: [] }), - branch: getBranchName(this.props.branch), + ...getBranchLikeQuery(this.props.branchLike), id: this.props.component && this.props.component.key, myIssues: myIssues ? 'true' : undefined } @@ -637,7 +642,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...DEFAULT_QUERY, - branch: getBranchName(this.props.branch), + ...getBranchLikeQuery(this.props.branchLike), id: this.props.component && this.props.component.key, myIssues: this.state.myIssues ? 'true' : undefined } @@ -724,7 +729,7 @@ export default class App extends React.PureComponent { handleReload = () => { this.fetchFirstIssues(); - if (isShortLivingBranch(this.props.branch)) { + if (isShortLivingBranch(this.props.branchLike) || isPullRequest(this.props.branchLike)) { this.props.onBranchesChange(); } }; @@ -892,7 +897,7 @@ export default class App extends React.PureComponent { } renderList() { - const { branch, component, currentUser, organization } = this.props; + const { branchLike, component, currentUser, organization } = this.props; const { issues, openIssue, paging } = this.state; const selectedIndex = this.getSelectedIndex(); const selectedIssue = selectedIndex != null ? issues[selectedIndex] : null; @@ -905,7 +910,7 @@ export default class App extends React.PureComponent { <div> {paging.total > 0 && ( <IssuesList - branch={getBranchName(branch)} + branchLike={branchLike} checked={this.state.checked} component={component} issues={issues} @@ -971,7 +976,7 @@ export default class App extends React.PureComponent { {openIssue != null ? ( <div className="pull-left width-60"> <ComponentBreadcrumbs - branch={getBranchName(this.props.branch)} + branchLike={this.props.branchLike} component={component} issue={openIssue} organization={this.props.organization} @@ -1000,7 +1005,7 @@ export default class App extends React.PureComponent { <div> {openIssue ? ( <IssuesSourceViewer - branch={getBranchName(this.props.branch)} + branchLike={this.props.branchLike} component={component} openIssue={openIssue} loadIssues={this.fetchIssuesForComponent} diff --git a/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx b/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx index 9da37298c41..c86ec19b057 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx @@ -21,11 +21,11 @@ import * as React from 'react'; import { Link } from 'react-router'; import Organization from '../../../components/shared/Organization'; import { collapsePath, limitComponentName } from '../../../helpers/path'; -import { getProjectUrl } from '../../../helpers/urls'; -import { Component } from '../../../app/types'; +import { getBranchLikeUrl, getCodeUrl } from '../../../helpers/urls'; +import { Component, BranchLike } from '../../../app/types'; interface Props { - branch?: string; + branchLike?: BranchLike; component?: Component; issue: { component: string; @@ -39,7 +39,12 @@ interface Props { organization?: { key: string }; } -export default function ComponentBreadcrumbs({ branch, component, issue, organization }: Props) { +export default function ComponentBreadcrumbs({ + branchLike, + component, + issue, + organization +}: Props) { const displayOrganization = !organization && (component == null || ['VW', 'SVW'].includes(component.qualifier)); const displayProject = component == null || !['TRK', 'BRC', 'DIR'].includes(component.qualifier); @@ -53,7 +58,7 @@ export default function ComponentBreadcrumbs({ branch, component, issue, organiz {displayProject && ( <span title={issue.projectName}> - <Link to={getProjectUrl(issue.project, branch)} className="link-no-underline"> + <Link to={getBranchLikeUrl(issue.project, branchLike)} className="link-no-underline"> {limitComponentName(issue.projectName)} </Link> <span className="slash-separator" /> @@ -64,14 +69,16 @@ export default function ComponentBreadcrumbs({ branch, component, issue, organiz issue.subProject !== undefined && issue.subProjectName !== undefined && ( <span title={issue.subProjectName}> - <Link to={getProjectUrl(issue.subProject, branch)} className="link-no-underline"> + <Link to={getBranchLikeUrl(issue.subProject, branchLike)} className="link-no-underline"> {limitComponentName(issue.subProjectName)} </Link> <span className="slash-separator" /> </span> )} - <Link to={getProjectUrl(issue.component, branch)} className="link-no-underline"> + <Link + to={getCodeUrl(issue.project, branchLike, issue.component)} + className="link-no-underline"> <span title={issue.componentLongName}>{collapsePath(issue.componentLongName)}</span> </Link> </div> diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js index 30506292162..c7d6bc2e650 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js @@ -25,7 +25,7 @@ import ListItem from './ListItem'; /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, checked: Array<string>, component?: Component, issues: Array<Issue>, @@ -44,13 +44,13 @@ export default class IssuesList extends React.PureComponent { /*:: props: Props; */ render() { - const { branch, checked, component, issues, openPopup, selectedIssue } = this.props; + const { branchLike, checked, component, issues, openPopup, selectedIssue } = this.props; return ( <div> {issues.map((issue, index) => ( <ListItem - branch={branch} + branchLike={branchLike} checked={checked.includes(issue.key)} component={component} key={issue.key} diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js index 6f7422c2290..fb72e2eca3b 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js @@ -26,7 +26,7 @@ import { scrollToElement } from '../../../helpers/scrolling'; /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, component: Component, loadIssues: (string, number, number) => Promise<*>, onIssueChange: Issue => void, @@ -107,7 +107,7 @@ export default class IssuesSourceViewer extends React.PureComponent { <div ref={node => (this.node = node)}> <SourceViewer aroundLine={aroundLine} - branch={this.props.branch} + branchLike={this.props.branchLike} component={openIssue.component} displayAllIssues={true} displayIssueLocationsCount={false} diff --git a/server/sonar-web/src/main/js/apps/issues/components/ListItem.js b/server/sonar-web/src/main/js/apps/issues/components/ListItem.js index 28c6ae69760..e4802fc64a3 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/ListItem.js +++ b/server/sonar-web/src/main/js/apps/issues/components/ListItem.js @@ -26,7 +26,7 @@ import Issue from '../../../components/issue/Issue'; /*:: type Props = {| - branch?: string, + branchLike?: { id?: string; name: string }, checked: boolean, component?: Component, issue: IssueType, @@ -89,7 +89,7 @@ export default class ListItem extends React.PureComponent { }; render() { - const { branch, component, issue, previousIssue } = this.props; + const { branchLike, component, issue, previousIssue } = this.props; const displayComponent = previousIssue == null || previousIssue.component !== issue.component; @@ -98,7 +98,7 @@ export default class ListItem extends React.PureComponent { {displayComponent && ( <div className="issues-workspace-list-component"> <ComponentBreadcrumbs - branch={branch} + branchLike={branchLike} component={component} issue={this.props.issue} organization={this.props.organization} @@ -106,7 +106,7 @@ export default class ListItem extends React.PureComponent { </div> )} <Issue - branch={branch} + branchLike={branchLike} checked={this.props.checked} displayLocationsLink={false} issue={issue} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx index 3bee431c81c..00c83d10dcd 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import ComponentBreadcrumbs from '../ComponentBreadcrumbs'; +import { ShortLivingBranch, BranchType } from '../../../../app/types'; const baseIssue = { component: 'comp', @@ -40,5 +41,13 @@ it('renders with sub-project', () => { it('renders with branch', () => { const issue = { ...baseIssue, subProject: 'sub-proj', subProjectName: 'sub-proj-name' }; - expect(shallow(<ComponentBreadcrumbs branch="feature" issue={issue} />)).toMatchSnapshot(); + const shortBranch: ShortLivingBranch = { + isMain: false, + mergeBranch: '', + name: 'feature', + type: BranchType.SHORT + }; + expect( + shallow(<ComponentBreadcrumbs branchLike={shortBranch} issue={issue} />) + ).toMatchSnapshot(); }); 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 3861f4a0780..ac510c72824 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 @@ -19,7 +19,6 @@ exports[`renders 1`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "proj", }, } @@ -37,10 +36,10 @@ exports[`renders 1`] = ` style={Object {}} to={ Object { - "pathname": "/dashboard", + "pathname": "/code", "query": Object { - "branch": undefined, - "id": "comp", + "id": "proj", + "selected": "comp", }, } } @@ -71,10 +70,11 @@ exports[`renders with branch 1`] = ` style={Object {}} to={ Object { - "pathname": "/dashboard", + "pathname": "/project/issues", "query": Object { "branch": "feature", "id": "proj", + "resolved": "false", }, } } @@ -94,10 +94,11 @@ exports[`renders with branch 1`] = ` style={Object {}} to={ Object { - "pathname": "/dashboard", + "pathname": "/project/issues", "query": Object { "branch": "feature", "id": "sub-proj", + "resolved": "false", }, } } @@ -114,10 +115,11 @@ exports[`renders with branch 1`] = ` style={Object {}} to={ Object { - "pathname": "/dashboard", + "pathname": "/code", "query": Object { "branch": "feature", - "id": "comp", + "id": "proj", + "selected": "comp", }, } } @@ -150,7 +152,6 @@ exports[`renders with sub-project 1`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "proj", }, } @@ -173,7 +174,6 @@ exports[`renders with sub-project 1`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "sub-proj", }, } @@ -191,10 +191,10 @@ exports[`renders with sub-project 1`] = ` style={Object {}} to={ Object { - "pathname": "/dashboard", + "pathname": "/code", "query": Object { - "branch": undefined, - "id": "comp", + "id": "proj", + "selected": "comp", }, } } diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx index 3592c4daea3..ebea71b75a6 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx +++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx @@ -21,15 +21,16 @@ import * as React from 'react'; import BadgeButton from './BadgeButton'; import BadgeParams from './BadgeParams'; import { BadgeType, BadgeOptions, getBadgeUrl } from './utils'; -import { Metric } from '../../../app/types'; +import { Metric, BranchLike } from '../../../app/types'; import CodeSnippet from '../../../components/common/CodeSnippet'; import Modal from '../../../components/controls/Modal'; +import { getBranchLikeQuery } from '../../../helpers/branches'; import { translate } from '../../../helpers/l10n'; import './styles.css'; import { Button, ResetButtonLink } from '../../../components/ui/buttons'; interface Props { - branch?: string; + branchLike?: BranchLike; metrics: { [key: string]: Metric }; project: string; } @@ -64,10 +65,10 @@ export default class BadgesModal extends React.PureComponent<Props, State> { }; render() { - const { branch, project } = this.props; + const { branchLike, project } = this.props; const { selectedType, badgeOptions } = this.state; const header = translate('overview.badges.title'); - const fullBadgeOptions = { branch, project, ...badgeOptions }; + const fullBadgeOptions = { project, ...badgeOptions, ...getBranchLikeQuery(branchLike) }; return ( <div className="overview-meta-card"> <Button className="js-project-badges" onClick={this.handleOpen}> diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx index cea857f2e90..d77c077d6d9 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx @@ -21,13 +21,20 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import BadgesModal from '../BadgesModal'; import { click } from '../../../../helpers/testUtils'; +import { ShortLivingBranch, BranchType } from '../../../../app/types'; jest.mock('../../../../helpers/urls', () => ({ getHostUrl: () => 'host' })); it('should display the modal after click', () => { - const wrapper = shallow(<BadgesModal branch="branch-6.6" metrics={{}} project="foo" />); + const shortBranch: ShortLivingBranch = { + isMain: false, + mergeBranch: '', + name: 'branch-6.6', + type: BranchType.SHORT + }; + const wrapper = shallow(<BadgesModal branchLike={shortBranch} metrics={{}} project="foo" />); expect(wrapper).toMatchSnapshot(); click(wrapper.find('Button')); expect(wrapper.find('Modal')).toMatchSnapshot(); diff --git a/server/sonar-web/src/main/js/apps/overview/badges/utils.ts b/server/sonar-web/src/main/js/apps/overview/badges/utils.ts index 39113f88945..5f300830c1b 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/utils.ts +++ b/server/sonar-web/src/main/js/apps/overview/badges/utils.ts @@ -28,6 +28,7 @@ export interface BadgeOptions { color?: BadgeColors; project?: string; metric?: string; + pullRequest?: string; } export enum BadgeType { @@ -38,19 +39,19 @@ export enum BadgeType { export function getBadgeUrl( type: BadgeType, - { branch, project, color = 'white', metric = 'alert_status' }: BadgeOptions + { branch, project, color = 'white', metric = 'alert_status', pullRequest }: BadgeOptions ) { switch (type) { case BadgeType.marketing: return `${getHostUrl()}/images/project_badges/sonarcloud-${color}.svg`; case BadgeType.qualityGate: return `${getHostUrl()}/api/project_badges/quality_gate?${stringify( - omitNil({ branch, project }) + omitNil({ branch, project, pullRequest }) )}`; case BadgeType.measure: default: return `${getHostUrl()}/api/project_badges/measure?${stringify( - omitNil({ branch, project, metric }) + omitNil({ branch, project, metric, pullRequest }) )}`; } } diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.tsx b/server/sonar-web/src/main/js/apps/overview/components/App.tsx index 03d898d4e9e..0d7caeb5d3e 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/App.tsx @@ -21,12 +21,12 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import OverviewApp from './OverviewApp'; import EmptyOverview from './EmptyOverview'; -import { getBranchName, isShortLivingBranch } from '../../../helpers/branches'; -import { getProjectBranchUrl, getCodeUrl } from '../../../helpers/urls'; -import { Branch, Component } from '../../../app/types'; +import { Component, BranchLike } from '../../../app/types'; +import { isShortLivingBranch } from '../../../helpers/branches'; +import { getShortLivingBranchUrl, getCodeUrl } from '../../../helpers/urls'; interface Props { - branch?: Branch; + branchLike?: BranchLike; component: Component; isInProgress?: boolean; isPending?: boolean; @@ -39,7 +39,7 @@ export default class App extends React.PureComponent<Props> { }; componentDidMount() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; if (this.isPortfolio()) { this.context.router.replace({ @@ -48,10 +48,10 @@ export default class App extends React.PureComponent<Props> { }); } else if (this.isFile()) { this.context.router.replace( - getCodeUrl(component.breadcrumbs[0].key, getBranchName(branch), component.key) + getCodeUrl(component.breadcrumbs[0].key, branchLike, component.key) ); - } else if (isShortLivingBranch(branch)) { - this.context.router.replace(getProjectBranchUrl(component.key, branch)); + } else if (isShortLivingBranch(branchLike)) { + this.context.router.replace(getShortLivingBranchUrl(component.key, branchLike.name)); } } @@ -60,9 +60,9 @@ export default class App extends React.PureComponent<Props> { isFile = () => ['FIL', 'UTS'].includes(this.props.component.qualifier); render() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; - if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branch)) { + if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branchLike)) { return null; } @@ -77,7 +77,7 @@ export default class App extends React.PureComponent<Props> { return ( <OverviewApp - branch={branch} + branchLike={branchLike} component={component} onComponentChange={this.props.onComponentChange} /> 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 5bc3679f9df..091826a44a6 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 @@ -36,14 +36,14 @@ import { getLeakPeriod, Period } from '../../../helpers/periods'; import { getCustomGraph, getGraph } from '../../../helpers/storage'; import { METRICS, HISTORY_METRICS_LIST } from '../utils'; import { DEFAULT_GRAPH, getDisplayedHistoryMetrics } from '../../projectActivity/utils'; -import { getBranchName } from '../../../helpers/branches'; +import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; import { fetchMetrics } from '../../../store/rootActions'; import { getMetrics } from '../../../store/rootReducer'; -import { Branch, Component, Metric } from '../../../app/types'; +import { BranchLike, Component, Metric } from '../../../app/types'; import '../styles.css'; interface OwnProps { - branch?: Branch; + branchLike?: BranchLike; component: Component; onComponentChange: (changes: {}) => void; } @@ -79,7 +79,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { componentDidUpdate(prevProps: Props) { if ( this.props.component.key !== prevProps.component.key || - this.props.branch !== prevProps.branch + !isSameBranchLike(this.props.branchLike, prevProps.branchLike) ) { this.loadMeasures().then(this.loadHistory, () => {}); } @@ -90,12 +90,12 @@ export class OverviewApp extends React.PureComponent<Props, State> { } loadMeasures() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; this.setState({ loading: true }); return getMeasuresAndMeta(component.key, METRICS, { additionalFields: 'metrics,periods', - branch: getBranchName(branch) + ...getBranchLikeQuery(branchLike) }).then( r => { if (this.mounted && r.metrics) { @@ -116,7 +116,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { } loadHistory = () => { - const { branch, component } = this.props; + const { branchLike, component } = this.props; let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph()); if (!graphMetrics || graphMetrics.length <= 0) { @@ -124,22 +124,24 @@ export class OverviewApp extends React.PureComponent<Props, State> { } const metrics = uniq(HISTORY_METRICS_LIST.concat(graphMetrics)); - return getAllTimeMachineData(component.key, metrics, { branch: getBranchName(branch) }).then( - r => { - if (this.mounted) { - const history: History = {}; - r.measures.forEach(measure => { - const measureHistory = measure.history.map(analysis => ({ - date: parseDate(analysis.date), - value: analysis.value - })); - history[measure.metric] = measureHistory; - }); - const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date; - this.setState({ history, historyStartDate }); - } + return getAllTimeMachineData({ + ...getBranchLikeQuery(branchLike), + component: component.key, + metrics: metrics.join() + }).then(r => { + if (this.mounted) { + const history: History = {}; + r.measures.forEach(measure => { + const measureHistory = measure.history.map(analysis => ({ + date: parseDate(analysis.date), + value: analysis.value + })); + history[measure.metric] = measureHistory; + }); + const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date; + this.setState({ history, historyStartDate }); } - ); + }); }; getApplicationLeakPeriod = () => @@ -156,7 +158,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { } render() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; const { loading, measures, periods, history, historyStartDate } = this.state; if (loading) { @@ -165,9 +167,8 @@ export class OverviewApp extends React.PureComponent<Props, State> { const leakPeriod = component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods); - const branchName = getBranchName(branch); const domainProps = { - branch: branchName, + branchLike, component, measures, leakPeriod, @@ -182,7 +183,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { {component.qualifier === 'APP' ? ( <ApplicationQualityGate component={component} /> ) : ( - <QualityGate branch={branchName} component={component} measures={measures} /> + <QualityGate branchLike={branchLike} component={component} measures={measures} /> )} <div className="overview-domains-list"> @@ -195,7 +196,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { <div className="overview-sidebar page-sidebar-fixed"> <Meta - branch={branchName} + branchLike={branchLike} component={component} history={history} measures={measures} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx index 4edd63ffba8..f3eb8d43cba 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx @@ -54,7 +54,7 @@ it('redirects on Code page for files', () => { qualifier: 'FIL' }; const replace = jest.fn(); - mount(<App branch={branch} component={newComponent} onComponentChange={jest.fn()} />, { + mount(<App branchLike={branch} component={newComponent} onComponentChange={jest.fn()} />, { context: { router: { replace } } }); expect(replace).toBeCalledWith({ diff --git a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx index 8380a53b0a5..d405662ace4 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx +++ b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx @@ -23,11 +23,13 @@ import Analysis from './Analysis'; import { getProjectActivity, Analysis as IAnalysis } from '../../../api/projectActivity'; import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; import { translate } from '../../../helpers/l10n'; -import { Metric, Component } from '../../../app/types'; +import { Metric, Component, BranchLike } from '../../../app/types'; import { History } from '../../../api/time-machine'; +import { getBranchLikeQuery } from '../../../helpers/branches'; +import { getActivityUrl } from '../../../helpers/urls'; interface Props { - branch?: string; + branchLike?: BranchLike; component: Component; history?: History; metrics: { [key: string]: Metric }; @@ -76,7 +78,7 @@ export default class AnalysesList extends React.PureComponent<Props, State> { this.setState({ loading: true }); getProjectActivity({ - branch: this.props.branch, + ...getBranchLikeQuery(this.props.branchLike), project: this.getTopLevelComponent(), ps: PAGE_SIZE }).then( @@ -101,7 +103,7 @@ export default class AnalysesList extends React.PureComponent<Props, State> { return ( <ul className="spacer-top"> {analyses.map(analysis => ( - <Analysis key={analysis.key} analysis={analysis} qualifier={this.props.qualifier} /> + <Analysis analysis={analysis} key={analysis.key} qualifier={this.props.qualifier} /> ))} </ul> ); @@ -121,20 +123,16 @@ export default class AnalysesList extends React.PureComponent<Props, State> { </h4> <PreviewGraph - branch={this.props.branch} + branchLike={this.props.branchLike} history={this.props.history} - project={this.props.component.key} metrics={this.props.metrics} + project={this.props.component.key} /> {this.renderList(analyses)} <div className="spacer-top small"> - <Link - to={{ - pathname: '/project/activity', - query: { id: this.props.component.key, branch: this.props.branch } - }}> + <Link to={getActivityUrl(this.props.component.key, this.props.branchLike)}> {translate('show_more')} </Link> </div> diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx b/server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx new file mode 100644 index 00000000000..710f2608a55 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import AnalysesList from '../AnalysesList'; + +it('should render show more link', () => { + const branchLike = { analysisDate: '2018-03-08T09:49:22+0100', isMain: true, name: 'master' }; + const component = { + breadcrumbs: [{ key: 'foo', name: 'foo', qualifier: 'TRK' }], + key: 'foo', + name: 'foo', + organization: 'org', + qualifier: 'TRK' + }; + const wrapper = shallow( + <AnalysesList branchLike={branchLike} component={component} metrics={{}} qualifier="TRK" /> + ); + wrapper.setState({ loading: false }); + expect(wrapper.find('Link')).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap new file mode 100644 index 00000000000..2320d0d0c4f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render show more link 1`] = ` +<Link + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/project/activity", + "query": Object { + "id": "foo", + }, + } + } +> + show_more +</Link> +`; 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 d6433a10e08..52fd2d5253b 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 @@ -31,7 +31,7 @@ import { translate } from '../../../helpers/l10n'; export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> { renderHeader() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; return ( <div className="overview-card-header"> @@ -39,13 +39,13 @@ export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> { <span>{translate('metric.bugs.name')}</span> <Link className="button button-small spacer-left text-text-bottom" - to={getComponentDrilldownUrl(component.key, 'Reliability', branch)}> + to={getComponentDrilldownUrl(component.key, 'Reliability', branchLike)}> <BubblesIcon size={14} /> </Link> <span className="big-spacer-left">{translate('metric.vulnerabilities.name')}</span> <Link className="button button-small spacer-left text-text-bottom" - to={getComponentDrilldownUrl(component.key, 'Security', branch)}> + to={getComponentDrilldownUrl(component.key, 'Security', branchLike)}> <BubblesIcon size={14} /> </Link> </div> diff --git a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx index ac7fcd0c94c..bc632fae96a 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx @@ -26,6 +26,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure, isDiffMetric } from '../../../helpers/measures'; import { getComponentIssuesUrl } from '../../../helpers/urls'; import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon'; +import { getBranchLikeQuery } from '../../../helpers/branches'; export class CodeSmells extends React.PureComponent<ComposedProps> { renderHeader() { @@ -33,10 +34,15 @@ export class CodeSmells extends React.PureComponent<ComposedProps> { } renderDebt(metric: string, type: string) { - const { branch, measures, component } = this.props; + const { branchLike, measures, component } = this.props; const measure = measures.find(measure => measure.metric.key === metric); const value = measure ? this.props.getValue(measure) : undefined; - const params = { branch, resolved: 'false', facetMode: 'effort', types: type }; + const params = { + ...getBranchLikeQuery(branchLike), + resolved: 'false', + facetMode: 'effort', + types: type + }; if (isDiffMetric(metric)) { Object.assign(params, { sinceLeakPeriod: 'true' }); diff --git a/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx b/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx index cd1abc807ee..28e5a141ef7 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx @@ -44,7 +44,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { } renderCoverage() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; const metric = 'coverage'; const coverage = this.getCoverage(); @@ -56,7 +56,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { <div className="display-inline-block text-middle"> <div className="overview-domain-measure-value"> - <DrilldownLink branch={branch} component={component.key} metric={metric}> + <DrilldownLink branchLike={branchLike} component={component.key} metric={metric}> <span className="js-overview-main-coverage"> {formatMeasure(coverage, 'PERCENT')} </span> @@ -73,7 +73,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { } renderNewCoverage() { - const { branch, component, leakPeriod, measures } = this.props; + const { branchLike, component, leakPeriod, measures } = this.props; if (!leakPeriod) { return null; } @@ -85,7 +85,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { newCoverageMeasure && newCoverageValue !== undefined ? ( <div> <DrilldownLink - branch={branch} + branchLike={branchLike} component={component.key} metric={newCoverageMeasure.metric.key}> <span className="js-overview-main-new-coverage"> @@ -106,7 +106,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { {translate('overview.coverage_on')} <br /> <DrilldownLink - branch={branch} + branchLike={branchLike} className="spacer-right overview-domain-secondary-measure-value" component={component.key} metric={newLinesToCover.metric.key}> diff --git a/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx b/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx index d8a09caee40..49db5f74c36 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx @@ -39,8 +39,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { } renderDuplications() { - const { branch, component, measures } = this.props; - + const { branchLike, component, measures } = this.props; const measure = measures.find(measure => measure.metric.key === 'duplicated_lines_density'); if (!measure) { return null; @@ -57,7 +56,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { <div className="display-inline-block text-middle"> <div className="overview-domain-measure-value"> <DrilldownLink - branch={branch} + branchLike={branchLike} component={component.key} metric="duplicated_lines_density"> {formatMeasure(duplications, 'PERCENT')} @@ -74,11 +73,10 @@ export class Duplications extends React.PureComponent<ComposedProps> { } renderNewDuplications() { - const { branch, component, measures, leakPeriod } = this.props; + const { branchLike, component, measures, leakPeriod } = this.props; if (!leakPeriod) { return null; } - const newDuplicationsMeasure = measures.find( measure => measure.metric.key === 'new_duplicated_lines_density' ); @@ -88,7 +86,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { newDuplicationsMeasure && newDuplicationsValue ? ( <div> <DrilldownLink - branch={branch} + branchLike={branchLike} component={component.key} metric={newDuplicationsMeasure.metric.key}> <span className="js-overview-main-new-duplications"> @@ -108,7 +106,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { {translate('overview.duplications_on')} <br /> <DrilldownLink - branch={branch} + branchLike={branchLike} className="spacer-right overview-domain-secondary-measure-value" component={component.key} metric={newLinesMeasure.metric.key}> diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx index 2b07c3c9239..2251612e2e3 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx @@ -40,11 +40,12 @@ import { getComponentIssuesUrl, getMeasureHistoryUrl } from '../../../helpers/urls'; -import { Component } from '../../../app/types'; +import { Component, BranchLike } from '../../../app/types'; import { History } from '../../../api/time-machine'; +import { getBranchLikeQuery } from '../../../helpers/branches'; export interface EnhanceProps { - branch?: string; + branchLike?: BranchLike; component: Component; measures: MeasureEnhanced[]; leakPeriod?: { index: number; date?: string }; @@ -77,14 +78,14 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP }; renderHeader = (domain: string, label: string) => { - const { branch, component } = this.props; + const { branchLike, component } = this.props; return ( <div className="overview-card-header"> <div className="overview-title"> <span>{label}</span> <Link className="button button-small spacer-left text-text-bottom" - to={getComponentDrilldownUrl(component.key, domain, branch)}> + to={getComponentDrilldownUrl(component.key, domain, branchLike)}> <BubblesIcon size={14} /> </Link> </div> @@ -93,7 +94,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP }; renderMeasure = (metricKey: string) => { - const { branch, measures, component } = this.props; + const { branchLike, measures, component } = this.props; const measure = measures.find(measure => measure.metric.key === metricKey); if (!measure) { return null; @@ -102,7 +103,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP return ( <div className="overview-domain-measure"> <div className="overview-domain-measure-value"> - <DrilldownLink branch={branch} component={component.key} metric={metricKey}> + <DrilldownLink branchLike={branchLike} component={component.key} metric={metricKey}> <span className="js-overview-main-tests"> {formatMeasure(measure.value, getShortType(measure.metric.type))} </span> @@ -118,7 +119,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP }; renderRating = (metricKey: string) => { - const { branch, component, measures } = this.props; + const { branchLike, component, measures } = this.props; const measure = measures.find(measure => measure.metric.key === metricKey); if (!measure) { return null; @@ -130,7 +131,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP <Tooltip overlay={title} placement="top"> <div className="overview-domain-measure-sup"> <DrilldownLink - branch={branch} + branchLike={branchLike} className="link-no-underline" component={component.key} metric={metricKey}> @@ -142,14 +143,14 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP }; renderIssues = (metric: string, type: string) => { - const { branch, measures, component } = this.props; + const { branchLike, measures, component } = this.props; const measure = measures.find(measure => measure.metric.key === metric); if (!measure) { return null; } const value = this.getValue(measure); - const params = { branch, resolved: 'false', types: type }; + const params = { ...getBranchLikeQuery(branchLike), resolved: 'false', types: type }; if (isDiffMetric(metric)) { Object.assign(params, { sinceLeakPeriod: 'true' }); } @@ -166,7 +167,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP return ( <Link className={linkClass} - to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branch)}> + to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branchLike)}> <HistoryIcon /> </Link> ); diff --git a/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx b/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx index 4a55b5e29a4..edca9b4c4cb 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx @@ -28,13 +28,13 @@ import MetaSize from './MetaSize'; import MetaTags from './MetaTags'; import BadgesModal from '../badges/BadgesModal'; import AnalysesList from '../events/AnalysesList'; -import { Visibility, Component, Metric } from '../../../app/types'; +import { Visibility, Component, Metric, BranchLike } from '../../../app/types'; import { History } from '../../../api/time-machine'; import { translate } from '../../../helpers/l10n'; import { MeasureEnhanced } from '../../../helpers/measures'; interface Props { - branch?: string; + branchLike?: BranchLike; component: Component; history?: History; measures: MeasureEnhanced[]; @@ -50,7 +50,7 @@ export default class Meta extends React.PureComponent<Props> { render() { const { onSonarCloud, organizationsEnabled } = this.context; - const { branch, component, metrics } = this.props; + const { branchLike, component, metrics } = this.props; const { qualifier, description, qualityProfiles, qualityGate, visibility } = component; const isProject = qualifier === 'TRK'; @@ -66,11 +66,11 @@ export default class Meta extends React.PureComponent<Props> { {isProject && ( <MetaTags component={component} onComponentChange={this.props.onComponentChange} /> )} - <MetaSize branch={branch} component={component} measures={this.props.measures} /> + <MetaSize branchLike={branchLike} component={component} measures={this.props.measures} /> </div> <AnalysesList - branch={branch} + branchLike={branchLike} component={component} history={this.props.history} metrics={metrics} @@ -106,7 +106,9 @@ export default class Meta extends React.PureComponent<Props> { {onSonarCloud && isProject && - !isPrivate && <BadgesModal branch={branch} metrics={metrics} project={component.key} />} + !isPrivate && ( + <BadgesModal branchLike={branchLike} metrics={metrics} project={component.key} /> + )} </div> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx index 335d0b48795..0fe760979f5 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx @@ -25,10 +25,10 @@ import SizeRating from '../../../components/ui/SizeRating'; import { formatMeasure, MeasureEnhanced } from '../../../helpers/measures'; import { getMetricName } from '../helpers/metrics'; import { translate } from '../../../helpers/l10n'; -import { LightComponent } from '../../../app/types'; +import { LightComponent, BranchLike } from '../../../app/types'; interface Props { - branch?: string; + branchLike?: BranchLike; component: LightComponent; measures: MeasureEnhanced[]; } @@ -43,7 +43,10 @@ export default class MetaSize extends React.PureComponent<Props> { <span className="spacer-right"> <SizeRating value={Number(ncloc.value)} /> </span> - <DrilldownLink branch={this.props.branch} component={this.props.component.key} metric="ncloc"> + <DrilldownLink + branchLike={this.props.branchLike} + component={this.props.component.key} + metric="ncloc"> {formatMeasure(ncloc.value, 'SHORT_INT')} </DrilldownLink> <div className="spacer-top text-muted">{getMetricName('ncloc')}</div> @@ -71,7 +74,7 @@ export default class MetaSize extends React.PureComponent<Props> { return projects ? ( <div id="overview-projects" className="overview-meta-size-ncloc is-half-width"> <DrilldownLink - branch={this.props.branch} + branchLike={this.props.branchLike} component={this.props.component.key} metric="projects"> {formatMeasure(projects.value, 'SHORT_INT')} diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js index 5c3125aedee..20ab7885146 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js @@ -38,13 +38,13 @@ function isProject(component /*: Component */) { /*:: type Props = { - branch?: string, + branchLike?: {id?: string; name: string }, component: Component, measures: MeasuresList }; */ -export default function QualityGate({ branch, component, measures } /*: Props */) { +export default function QualityGate({ branchLike, component, measures } /*: Props */) { const statusMeasure = measures.find(measure => measure.metric.key === 'alert_status'); const detailsMeasure = measures.find(measure => measure.metric.key === 'quality_gate_details'); @@ -81,7 +81,11 @@ export default function QualityGate({ branch, component, measures } /*: Props */ )} {conditions.length > 0 && ( - <QualityGateConditions branch={branch} component={component} conditions={conditions} /> + <QualityGateConditions + branchLike={branchLike} + component={component} + conditions={conditions} + /> )} </div> ); diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js index 3301c33ef6f..ba6d12fa35d 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js @@ -27,12 +27,13 @@ import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; import { getPeriodValue, isDiffMetric, formatMeasure } from '../../../helpers/measures'; import { translate } from '../../../helpers/l10n'; import { getComponentIssuesUrl } from '../../../helpers/urls'; +import { getBranchLikeQuery } from '../../../helpers/branches'; /*:: import type { Component } from '../types'; */ /*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ export default class QualityGateCondition extends React.PureComponent { /*:: props: { - branch?: string, + branchLike?: { id?: string; name: string }, component: Component, condition: { level: string, @@ -54,7 +55,11 @@ export default class QualityGateCondition extends React.PureComponent { } getIssuesUrl = (sinceLeakPeriod /*: boolean */, customQuery /*: {} */) => { - const query /*: Object */ = { resolved: 'false', branch: this.props.branch, ...customQuery }; + const query /*: Object */ = { + resolved: 'false', + ...getBranchLikeQuery(this.props.branchLike), + ...customQuery + }; if (sinceLeakPeriod) { Object.assign(query, { sinceLeakPeriod: 'true' }); } @@ -89,7 +94,7 @@ export default class QualityGateCondition extends React.PureComponent { } wrapWithLink(children /*: React.Element<*> */) { - const { branch, component, condition } = this.props; + const { branchLike, component, condition } = this.props; const className = classNames( 'overview-quality-gate-condition', @@ -114,7 +119,7 @@ export default class QualityGateCondition extends React.PureComponent { </Link> ) : ( <DrilldownLink - branch={branch} + branchLike={branchLike} className={className} component={component.key} metric={condition.measure.metric.key} diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js index 8abd92619a1..57dc3002b4d 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js @@ -19,10 +19,12 @@ */ import React from 'react'; import { sortBy } from 'lodash'; +import PropTypes from 'prop-types'; import QualityGateCondition from './QualityGateCondition'; import { ComponentType, ConditionsListType } from '../propTypes'; import { getMeasuresAndMeta } from '../../../api/measures'; import { enhanceMeasuresWithMetrics } from '../../../helpers/measures'; +import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; const LEVEL_ORDER = ['ERROR', 'WARN']; @@ -35,7 +37,7 @@ function enhanceConditions(conditions, measures) { export default class QualityGateConditions extends React.PureComponent { static propTypes = { - // branch + branchLike: PropTypes.object, component: ComponentType.isRequired, conditions: ConditionsListType.isRequired }; @@ -51,7 +53,7 @@ export default class QualityGateConditions extends React.PureComponent { componentDidUpdate(prevProps) { if ( - prevProps.branch !== this.props.branch || + !isSameBranchLike(prevProps.branchLike, this.props.branchLike) || prevProps.conditions !== this.props.conditions || prevProps.component !== this.props.component ) { @@ -64,13 +66,13 @@ export default class QualityGateConditions extends React.PureComponent { } loadFailedMeasures() { - const { branch, component, conditions } = this.props; + const { branchLike, component, conditions } = this.props; const failedConditions = conditions.filter(c => c.level !== 'OK'); if (failedConditions.length > 0) { const metrics = failedConditions.map(condition => condition.metric); getMeasuresAndMeta(component.key, metrics, { additionalFields: 'metrics', - branch + ...getBranchLikeQuery(branchLike) }).then(r => { if (this.mounted) { const measures = enhanceMeasuresWithMetrics(r.component.measures, r.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 64d2bf86cfb..4c5e4ee1a87 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,7 +9,6 @@ exports[`renders 1`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "foo", }, } diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap index e286b3da99d..900cc34dce2 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap @@ -9,7 +9,6 @@ exports[`new_maintainability_rating 1`] = ` Object { "pathname": "/project/issues", "query": Object { - "branch": undefined, "id": "abcd-key", "resolved": "false", "sinceLeakPeriod": "true", @@ -104,7 +103,6 @@ exports[`new_reliability_rating 1`] = ` Object { "pathname": "/project/issues", "query": Object { - "branch": undefined, "id": "abcd-key", "resolved": "false", "severities": "BLOCKER,CRITICAL,MAJOR,MINOR", @@ -158,7 +156,6 @@ exports[`new_security_rating 1`] = ` Object { "pathname": "/project/issues", "query": Object { - "branch": undefined, "id": "abcd-key", "resolved": "false", "severities": "BLOCKER,CRITICAL,MAJOR,MINOR", @@ -254,7 +251,6 @@ exports[`reliability_rating 1`] = ` Object { "pathname": "/project/issues", "query": Object { - "branch": undefined, "id": "abcd-key", "resolved": "false", "severities": "BLOCKER,CRITICAL,MAJOR,MINOR", @@ -307,7 +303,6 @@ exports[`security_rating 1`] = ` Object { "pathname": "/project/issues", "query": Object { - "branch": undefined, "id": "abcd-key", "resolved": "false", "severities": "BLOCKER,CRITICAL,MAJOR,MINOR", @@ -360,7 +355,6 @@ exports[`should work with branch 1`] = ` Object { "pathname": "/project/issues", "query": Object { - "branch": "feature", "id": "abcd-key", "resolved": "false", "sinceLeakPeriod": "true", @@ -413,7 +407,6 @@ exports[`sqale_rating 1`] = ` Object { "pathname": "/project/issues", "query": Object { - "branch": undefined, "id": "abcd-key", "resolved": "false", "types": "CODE_SMELL", diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx index ddba951e313..01d14bb1807 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx @@ -60,13 +60,13 @@ export default class Activity extends React.PureComponent<Props> { fetchHistory = () => { const { component } = this.props; - let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph()); + let graphMetrics: string[] = getDisplayedHistoryMetrics(getGraph(), getCustomGraph()); if (!graphMetrics || graphMetrics.length <= 0) { graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []); } this.setState({ loading: true }); - return getAllTimeMachineData(component, graphMetrics).then( + return getAllTimeMachineData({ component, metrics: graphMetrics.join() }).then( timeMachine => { if (this.mounted) { const history: History = {}; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx index 27d4e2d957b..aa0fd5f0a16 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx @@ -81,7 +81,7 @@ export class App extends React.PureComponent<Props, State> { fetchData() { this.setState({ loading: true }); Promise.all([ - getMeasures(this.props.component.key, PORTFOLIO_METRICS), + getMeasures({ componentKey: this.props.component.key, metricKeys: PORTFOLIO_METRICS.join() }), getChildren(this.props.component.key, SUB_COMPONENTS_METRICS, { ps: 20, s: 'qualifier' }) ]).then( ([measures, subComponents]) => { diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx index c8c2e81f295..0b5fb1f1584 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx @@ -67,5 +67,5 @@ it('renders', () => { it('fetches history', () => { mount(<Activity component="foo" metrics={{}} />); - expect(getAllTimeMachineData).toBeCalledWith('foo', ['coverage']); + expect(getAllTimeMachineData).toBeCalledWith({ component: 'foo', metrics: 'coverage' }); }); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx index 6cfe8d6890b..4abdc4e056b 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx @@ -77,23 +77,11 @@ it('fetches measures and children components', () => { getMeasures.mockClear(); getChildren.mockClear(); mount(<App component={component} fetchMetrics={jest.fn()} metrics={{}} />); - expect(getMeasures).toBeCalledWith('foo', [ - 'projects', - 'ncloc', - 'ncloc_language_distribution', - 'releasability_rating', - 'releasability_effort', - 'sqale_rating', - 'maintainability_rating_effort', - 'reliability_rating', - 'reliability_rating_effort', - 'security_rating', - 'security_rating_effort', - 'last_change_on_releasability_rating', - 'last_change_on_maintainability_rating', - 'last_change_on_security_rating', - 'last_change_on_reliability_rating' - ]); + expect(getMeasures).toBeCalledWith({ + componentKey: 'foo', + metricKeys: + 'projects,ncloc,ncloc_language_distribution,releasability_rating,releasability_effort,sqale_rating,maintainability_rating_effort,reliability_rating,reliability_rating_effort,security_rating,security_rating_effort,last_change_on_releasability_rating,last_change_on_maintainability_rating,last_change_on_security_rating,last_change_on_reliability_rating' + }); expect(getChildren).toBeCalledWith( 'foo', [ diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap index 8918c9e4b99..b278210c01b 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap @@ -16,7 +16,6 @@ exports[`renders 1`] = ` Object { "pathname": "/component_measures", "query": Object { - "branch": undefined, "id": "foo", "metric": "security_rating", }, diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap index e62b2d558c6..8f25ec024e1 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap @@ -9,7 +9,6 @@ exports[`renders 1`] = ` Object { "pathname": "/project/activity", "query": Object { - "branch": undefined, "custom_metrics": "security_rating", "graph": "custom", "id": "foo", diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap index d8cc0a6fd99..b90b5afa313 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap @@ -9,7 +9,6 @@ exports[`renders 1`] = ` Object { "pathname": "/component_measures", "query": Object { - "branch": undefined, "id": "foo", "metric": "security_rating", "view": "treemap", diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap index a97849bb45e..b4afa69ade1 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap @@ -9,7 +9,6 @@ exports[`renders 1`] = ` Object { "pathname": "/component_measures", "query": Object { - "branch": undefined, "id": "foo", "metric": "security_rating", }, diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap index c2120360b56..2401a4dc09b 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap @@ -17,7 +17,6 @@ exports[`renders 1`] = ` Object { "pathname": "/component_measures", "query": Object { - "branch": undefined, "id": "foo", "metric": "alert_status", }, @@ -41,7 +40,6 @@ exports[`renders 1`] = ` Object { "pathname": "/component_measures", "query": Object { - "branch": undefined, "id": "foo", "metric": "alert_status", }, diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap index 149c818b286..5c828719017 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap @@ -24,7 +24,6 @@ exports[`renders 1`] = ` Object { "pathname": "/component_measures", "query": Object { - "branch": undefined, "id": "foo", "metric": "projects", }, @@ -55,7 +54,6 @@ exports[`renders 1`] = ` Object { "pathname": "/component_measures", "query": Object { - "branch": undefined, "id": "foo", "metric": "ncloc", }, 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 736ab248060..ceb66108e3c 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,7 +53,6 @@ exports[`renders 1`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "foo", }, } @@ -141,7 +140,6 @@ exports[`renders 1`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "barbar", }, } @@ -229,7 +227,6 @@ exports[`renders 1`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "bazbaz", }, } diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js index 51d28646604..54aa9929ecc 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js @@ -26,7 +26,7 @@ import { getAllTimeMachineData } from '../../../api/time-machine'; import { getAllMetrics } from '../../../api/metrics'; import * as api from '../../../api/projectActivity'; import * as actions from '../actions'; -import { getBranchName } from '../../../helpers/branches'; +import { getBranchLikeQuery } from '../../../helpers/branches'; import { parseDate } from '../../../helpers/dates'; import { getCustomGraph, getGraph } from '../../../helpers/storage'; import { @@ -51,7 +51,7 @@ type Component = { }; type Props = { - branch?: {}, + branchLike?: { id?: string; name: string }, location: { pathname: string, query: RawQuery }, component: Component }; @@ -103,7 +103,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { pathname: this.props.location.pathname, query: { ...serializeUrlQuery(newQuery), - branch: getBranchName(this.props.branch) + ...getBranchLikeQuery(this.props.branchLike) } }); } else { @@ -169,12 +169,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { [string]: string } */ ) => { - const parameters = { - project, - p, - ps, - branch: getBranchName(this.props.branch) - }; + const parameters = { project, p, ps, ...getBranchLikeQuery(this.props.branchLike) }; return api .getProjectActivity({ ...additional, ...parameters }) .then(({ analyses, paging }) => ({ @@ -187,8 +182,10 @@ export default class ProjectActivityAppContainer extends React.PureComponent { if (metrics.length <= 0) { return Promise.resolve([]); } - return getAllTimeMachineData(this.props.component.key, metrics, { - branch: getBranchName(this.props.branch) + return getAllTimeMachineData({ + component: this.props.component.key, + metrics: metrics.join(), + ...getBranchLikeQuery(this.props.branchLike) }).then( ({ measures }) => measures.map(measure => ({ @@ -300,7 +297,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { pathname: this.props.location.pathname, query: { ...query, - branch: getBranchName(this.props.branch), + ...getBranchLikeQuery(this.props.branchLike), id: this.props.component.key } }); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx index e4c886668fd..24dac728e91 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx @@ -22,14 +22,19 @@ import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router'; import BranchRow from './BranchRow'; import LongBranchesPattern from './LongBranchesPattern'; -import { Branch } from '../../../app/types'; -import { sortBranchesAsTree } from '../../../helpers/branches'; +import { BranchLike } from '../../../app/types'; +import { + sortBranchesAsTree, + getBranchLikeKey, + isShortLivingBranch, + isPullRequest +} from '../../../helpers/branches'; import { translate } from '../../../helpers/l10n'; import { getValues } from '../../../api/settings'; import { formatMeasure } from '../../../helpers/measures'; interface Props { - branches: Branch[]; + branchLikes: BranchLike[]; canAdmin?: boolean; component: { key: string }; onBranchesChange: () => void; @@ -57,7 +62,7 @@ export default class App extends React.PureComponent<Props, State> { fetchPurgeSetting() { this.setState({ loading: true }); - getValues(BRANCH_LIFETIME_SETTING).then( + getValues({ keys: BRANCH_LIFETIME_SETTING }).then( settings => { if (this.mounted) { this.setState({ @@ -78,26 +83,29 @@ export default class App extends React.PureComponent<Props, State> { return null; } - const messageKey = this.props.canAdmin - ? 'project_branches.page.life_time.admin' - : 'project_branches.page.life_time'; - return ( <p className="page-description"> <FormattedMessage - defaultMessage={translate(messageKey)} - id={messageKey} - values={{ - days: formatMeasure(this.state.branchLifeTime, 'INT'), - settings: <Link to="/admin/settings">{translate('settings.page')}</Link> - }} + defaultMessage={translate('project_branches.page.life_time')} + id="project_branches.page.life_time" + values={{ days: formatMeasure(this.state.branchLifeTime, 'INT') }} /> + {this.props.canAdmin && ( + <> + <br /> + <FormattedMessage + defaultMessage={translate('project_branches.page.life_time.admin')} + id="project_branches.page.life_time.admin" + values={{ settings: <Link to="/admin/settings">{translate('settings.page')}</Link> }} + /> + </> + )} </p> ); } render() { - const { branches, component, onBranchesChange } = this.props; + const { branchLikes, component, onBranchesChange } = this.props; if (this.state.loading) { return ( @@ -132,11 +140,15 @@ export default class App extends React.PureComponent<Props, State> { </tr> </thead> <tbody> - {sortBranchesAsTree(branches).map(branch => ( + {sortBranchesAsTree(branchLikes).map(branchLike => ( <BranchRow - branch={branch} + branchLike={branchLike} component={component.key} - key={branch.name} + isOrphan={ + (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && + branchLike.isOrphan + } + key={getBranchLikeKey(branchLike)} onChange={onBranchesChange} /> ))} diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx index 723998c7049..0d5e440a372 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx @@ -22,10 +22,16 @@ import * as classNames from 'classnames'; import DeleteBranchModal from './DeleteBranchModal'; import LeakPeriodForm from './LeakPeriodForm'; import RenameBranchModal from './RenameBranchModal'; -import { Branch } from '../../../app/types'; +import { BranchLike } from '../../../app/types'; import BranchStatus from '../../../components/common/BranchStatus'; import BranchIcon from '../../../components/icons-components/BranchIcon'; -import { isShortLivingBranch, isLongLivingBranch } from '../../../helpers/branches'; +import { + isShortLivingBranch, + isLongLivingBranch, + isMainBranch, + getBranchLikeDisplayName, + isPullRequest +} from '../../../helpers/branches'; import { translate } from '../../../helpers/l10n'; import DateFromNow from '../../../components/intl/DateFromNow'; import ActionsDropdown, { @@ -34,8 +40,9 @@ import ActionsDropdown, { } from '../../../components/controls/ActionsDropdown'; interface Props { - branch: Branch; + branchLike: BranchLike; component: string; + isOrphan?: boolean; onChange: () => void; } @@ -91,19 +98,19 @@ export default class BranchRow extends React.PureComponent<Props, State> { }; renderActions() { - const { branch, component } = this.props; + const { branchLike, component } = this.props; return ( <td className="thin nowrap text-right"> <ActionsDropdown className="ig-spacer-left"> - {isLongLivingBranch(branch) && ( + {isLongLivingBranch(branchLike) && ( <ActionsDropdownItem className="js-change-leak-period" onClick={this.handleChangeLeakClick}> {translate('branches.set_leak_period')} </ActionsDropdownItem> )} - {isLongLivingBranch(branch) && !branch.isMain && <ActionsDropdownDivider />} - {branch.isMain ? ( + {isLongLivingBranch(branchLike) && <ActionsDropdownDivider />} + {isMainBranch(branchLike) ? ( <ActionsDropdownItem className="js-rename" onClick={this.handleRenameClick}> {translate('branches.rename')} </ActionsDropdownItem> @@ -112,62 +119,65 @@ export default class BranchRow extends React.PureComponent<Props, State> { className="js-delete" destructive={true} onClick={this.handleDeleteClick}> - {translate('branches.delete')} + {translate( + isPullRequest(branchLike) ? 'branches.pull_request.delete' : 'branches.delete' + )} </ActionsDropdownItem> )} </ActionsDropdown> {this.state.deleting && ( <DeleteBranchModal - branch={branch} + branchLike={branchLike} component={component} onClose={this.handleDeletingStop} onDelete={this.handleChange} /> )} - {this.state.renaming && ( - <RenameBranchModal - branch={branch} - component={component} - onClose={this.handleRenamingStop} - onRename={this.handleChange} - /> - )} + {this.state.renaming && + isMainBranch(branchLike) && ( + <RenameBranchModal + branch={branchLike} + component={component} + onClose={this.handleRenamingStop} + onRename={this.handleChange} + /> + )} - {this.state.changingLeak && ( - <LeakPeriodForm - branch={branch.name} - onClose={this.handleChangingLeakStop} - project={component} - /> - )} + {this.state.changingLeak && + isLongLivingBranch(branchLike) && ( + <LeakPeriodForm + branch={branchLike.name} + onClose={this.handleChangingLeakStop} + project={component} + /> + )} </td> ); } render() { - const { branch } = this.props; + const { branchLike, isOrphan } = this.props; + const indented = (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && !isOrphan; return ( <tr> <td> <BranchIcon - branch={branch} - className={classNames('little-spacer-right', { - 'big-spacer-left': isShortLivingBranch(branch) && !branch.isOrphan - })} + branchLike={branchLike} + className={classNames('little-spacer-right', { 'big-spacer-left': indented })} /> - {branch.name} - {branch.isMain && ( + {getBranchLikeDisplayName(branchLike)} + {isMainBranch(branchLike) && ( <div className="outline-badge spacer-left">{translate('branches.main_branch')}</div> )} </td> <td className="thin nowrap text-right"> - <BranchStatus branch={branch} /> + <BranchStatus branchLike={branchLike} /> </td> <td className="thin nowrap text-right"> - {branch.analysisDate && <DateFromNow date={branch.analysisDate} />} + {branchLike.analysisDate && <DateFromNow date={branchLike.analysisDate} />} </td> {this.renderActions()} </tr> diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx index 127b3781189..c98bb190f65 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx @@ -18,14 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { deleteBranch } from '../../../api/branches'; -import { Branch } from '../../../app/types'; +import { deleteBranch, deletePullRequest } from '../../../api/branches'; +import { BranchLike } from '../../../app/types'; import Modal from '../../../components/controls/Modal'; import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { isPullRequest, getBranchLikeDisplayName } from '../../../helpers/branches'; interface Props { - branch: Branch; + branchLike: BranchLike; component: string; onClose: () => void; onDelete: () => void; @@ -50,7 +51,16 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State> handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); this.setState({ loading: true }); - deleteBranch(this.props.component, this.props.branch.name).then( + const request = isPullRequest(this.props.branchLike) + ? deletePullRequest({ + project: this.props.component, + pullRequest: this.props.branchLike.key + }) + : deleteBranch({ + branch: this.props.branchLike.name, + project: this.props.component + }); + request.then( () => { if (this.mounted) { this.setState({ loading: false }); @@ -66,8 +76,10 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State> }; render() { - const { branch } = this.props; - const header = translate('branches.delete'); + const { branchLike } = this.props; + const header = translate( + isPullRequest(branchLike) ? 'branches.pull_request.delete' : 'branches.delete' + ); return ( <Modal contentLabel={header} onRequestClose={this.props.onClose}> @@ -76,7 +88,12 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State> </header> <form onSubmit={this.handleSubmit}> <div className="modal-body"> - {translateWithParameters('branches.delete.are_you_sure', branch.name)} + {translateWithParameters( + isPullRequest(branchLike) + ? 'branches.pull_request.delete.are_you_sure' + : 'branches.delete.are_you_sure', + getBranchLikeDisplayName(branchLike) + )} </div> <footer className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/LeakPeriodForm.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/LeakPeriodForm.tsx index 71d4deaa53f..3bb22c48aed 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/LeakPeriodForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/LeakPeriodForm.tsx @@ -53,7 +53,7 @@ export default class LeakPeriodForm extends React.PureComponent<Props, State> { fetchSetting() { this.setState({ loading: true }); - getValues(LEAK_PERIOD, this.props.project, this.props.branch).then( + getValues({ keys: LEAK_PERIOD, component: this.props.project, branch: this.props.branch }).then( settings => { if (this.mounted) { this.setState({ loading: false, setting: settings[0] }); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/LongBranchesPattern.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/LongBranchesPattern.tsx index e6d8b31d0f2..61c9e133c4c 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/LongBranchesPattern.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/LongBranchesPattern.tsx @@ -48,7 +48,7 @@ export default class LongBranchesPattern extends React.PureComponent<Props, Stat } fetchSetting() { - return getValues(LONG_BRANCH_PATTERN, this.props.project).then( + return getValues({ keys: LONG_BRANCH_PATTERN, component: this.props.project }).then( settings => { if (this.mounted) { this.setState({ setting: settings[0] }); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx index bc78f0395cb..2e8ee656d44 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx @@ -19,13 +19,13 @@ */ import * as React from 'react'; import { renameBranch } from '../../../api/branches'; -import { Branch } from '../../../app/types'; +import { MainBranch } from '../../../app/types'; import Modal from '../../../components/controls/Modal'; import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { - branch: Branch; + branch: MainBranch; component: string; onClose: () => void; onRename: () => void; diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx index 6c3b737dd4c..45533ab1da6 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx @@ -51,6 +51,12 @@ export default class SettingForm extends React.PureComponent<Props, State> { this.mounted = false; } + stopLoading = () => { + if (this.mounted) { + this.setState({ submitting: false }); + } + }; + handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); @@ -65,11 +71,7 @@ export default class SettingForm extends React.PureComponent<Props, State> { component: this.props.project, key: this.props.setting.key, value - }).then(this.props.onChange, () => { - if (this.mounted) { - this.setState({ submitting: false }); - } - }); + }).then(this.props.onChange, this.stopLoading); }; handleValueChange = (event: React.SyntheticEvent<HTMLInputElement>) => { @@ -78,14 +80,11 @@ export default class SettingForm extends React.PureComponent<Props, State> { handleResetClick = () => { this.setState({ submitting: true }); - resetSettingValue(this.props.setting.key, this.props.project, this.props.branch).then( - this.props.onChange, - () => { - if (this.mounted) { - this.setState({ submitting: false }); - } - } - ); + resetSettingValue({ + keys: this.props.setting.key, + component: this.props.project, + branch: this.props.branch + }).then(this.props.onChange, this.stopLoading); }; render() { diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx index e22b327bbc1..ee90b112e98 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx @@ -25,7 +25,13 @@ jest.mock('../../../../api/settings', () => ({ import * as React from 'react'; import { mount, shallow } from 'enzyme'; import App from '../App'; -import { BranchType, MainBranch, LongLivingBranch, ShortLivingBranch } from '../../../../app/types'; +import { + BranchType, + LongLivingBranch, + ShortLivingBranch, + MainBranch, + PullRequest +} from '../../../../app/types'; const getValues = require('../../../../api/settings').getValues as jest.Mock<any>; @@ -34,19 +40,27 @@ beforeEach(() => { }); it('renders sorted list of branches', () => { - const branches: [MainBranch, LongLivingBranch, ShortLivingBranch] = [ + const branchLikes: [MainBranch, LongLivingBranch, ShortLivingBranch, PullRequest] = [ { isMain: true, name: 'master' }, { isMain: false, name: 'branch-1.0', type: BranchType.LONG }, - { isMain: false, name: 'branch-1.0', mergeBranch: 'master', type: BranchType.SHORT } + { isMain: false, mergeBranch: 'master', name: 'feature', type: BranchType.SHORT }, + { base: 'master', branch: 'feature', key: '1234', title: 'Feature PR' } ]; const wrapper = shallow( - <App branches={branches} component={{ key: 'foo' }} onBranchesChange={jest.fn()} /> + <App + branchLikes={branchLikes} + canAdmin={true} + component={{ key: 'foo' }} + onBranchesChange={jest.fn()} + /> ); wrapper.setState({ branchLifeTime: '100', loading: false }); expect(wrapper).toMatchSnapshot(); }); it('fetches branch life time setting on mount', () => { - mount(<App branches={[]} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />); - expect(getValues).toBeCalledWith('sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches'); + mount(<App branchLikes={[]} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />); + expect(getValues).toBeCalledWith({ + keys: 'sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches' + }); }); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx index 8d7532033cc..eac799c7f6f 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx @@ -20,7 +20,13 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import BranchRow from '../BranchRow'; -import { MainBranch, ShortLivingBranch, BranchType } from '../../../../app/types'; +import { + MainBranch, + ShortLivingBranch, + BranchType, + PullRequest, + BranchLike +} from '../../../../app/types'; import { click } from '../../../../helpers/testUtils'; const mainBranch: MainBranch = { isMain: true, name: 'master' }; @@ -33,6 +39,13 @@ const shortBranch: ShortLivingBranch = { type: BranchType.SHORT }; +const pullRequest: PullRequest = { + base: 'master', + branch: 'feature', + key: '1234', + title: 'Feature PR' +}; + it('renders main branch', () => { expect(shallowRender(mainBranch)).toMatchSnapshot(); }); @@ -41,6 +54,10 @@ it('renders short-living branch', () => { expect(shallowRender(shortBranch)).toMatchSnapshot(); }); +it('renders pull request', () => { + expect(shallowRender(pullRequest)).toMatchSnapshot(); +}); + it('renames main branch', () => { const onChange = jest.fn(); const wrapper = shallowRender(mainBranch, onChange); @@ -59,8 +76,19 @@ it('deletes short-living branch', () => { expect(onChange).toBeCalled(); }); -function shallowRender(branch: MainBranch | ShortLivingBranch, onChange: () => void = jest.fn()) { - const wrapper = shallow(<BranchRow branch={branch} component="foo" onChange={onChange} />); +it('deletes pull request', () => { + const onChange = jest.fn(); + const wrapper = shallowRender(pullRequest, onChange); + + click(wrapper.find('.js-delete')); + (wrapper.find('DeleteBranchModal').prop('onDelete') as Function)(); + expect(onChange).toBeCalled(); +}); + +function shallowRender(branchLike: BranchLike, onChange: () => void = jest.fn()) { + const wrapper = shallow( + <BranchRow branchLike={branchLike} component="foo" isOrphan={false} onChange={onChange} /> + ); (wrapper.instance() as any).mounted = true; return wrapper; } diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx index 88fd8dffca7..16d7dfaf5cf 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx @@ -18,42 +18,72 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* eslint-disable import/first */ -jest.mock('../../../../api/branches', () => ({ deleteBranch: jest.fn() })); +jest.mock('../../../../api/branches', () => ({ + deleteBranch: jest.fn(), + deletePullRequest: jest.fn() +})); import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import DeleteBranchModal from '../DeleteBranchModal'; -import { ShortLivingBranch, BranchType } from '../../../../app/types'; +import { ShortLivingBranch, BranchType, BranchLike, PullRequest } from '../../../../app/types'; import { submit, doAsync, click, waitAndUpdate } from '../../../../helpers/testUtils'; -import { deleteBranch } from '../../../../api/branches'; +import { deleteBranch, deletePullRequest } from '../../../../api/branches'; + +const branch: ShortLivingBranch = { + isMain: false, + name: 'feature', + mergeBranch: 'master', + type: BranchType.SHORT +}; beforeEach(() => { - (deleteBranch as jest.Mock<any>).mockClear(); + (deleteBranch as jest.Mock).mockClear(); + (deletePullRequest as jest.Mock).mockClear(); }); it('renders', () => { - const wrapper = shallowRender(); + const wrapper = shallowRender(branch); expect(wrapper).toMatchSnapshot(); wrapper.setState({ loading: true }); expect(wrapper).toMatchSnapshot(); }); it('deletes branch', async () => { - (deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.resolve()); + (deleteBranch as jest.Mock).mockImplementationOnce(() => Promise.resolve()); + const onDelete = jest.fn(); + const wrapper = shallowRender(branch, onDelete); + + submitForm(wrapper); + + await waitAndUpdate(wrapper); + expect(wrapper.state().loading).toBe(false); + expect(onDelete).toBeCalled(); + expect(deleteBranch).toBeCalledWith({ branch: 'feature', project: 'foo' }); +}); + +it('deletes pull request', async () => { + (deletePullRequest as jest.Mock).mockImplementationOnce(() => Promise.resolve()); + const pullRequest: PullRequest = { + base: 'master', + branch: 'feature', + key: '1234', + title: 'Feature PR' + }; const onDelete = jest.fn(); - const wrapper = shallowRender(onDelete); + const wrapper = shallowRender(pullRequest, onDelete); submitForm(wrapper); await waitAndUpdate(wrapper); expect(wrapper.state().loading).toBe(false); expect(onDelete).toBeCalled(); - expect(deleteBranch).toBeCalledWith('foo', 'feature'); + expect(deletePullRequest).toBeCalledWith({ project: 'foo', pullRequest: '1234' }); }); it('cancels', () => { const onClose = jest.fn(); - const wrapper = shallowRender(jest.fn(), onClose); + const wrapper = shallowRender(branch, jest.fn(), onClose); click(wrapper.find('ResetButtonLink')); @@ -63,27 +93,30 @@ it('cancels', () => { }); it('stops loading on WS error', async () => { - (deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.reject(null)); + (deleteBranch as jest.Mock).mockImplementationOnce(() => Promise.reject(null)); const onDelete = jest.fn(); - const wrapper = shallowRender(onDelete); + const wrapper = shallowRender(branch, onDelete); submitForm(wrapper); await waitAndUpdate(wrapper); expect(wrapper.state().loading).toBe(false); expect(onDelete).not.toBeCalled(); - expect(deleteBranch).toBeCalledWith('foo', 'feature'); + expect(deleteBranch).toBeCalledWith({ branch: 'feature', project: 'foo' }); }); -function shallowRender(onDelete: () => void = jest.fn(), onClose: () => void = jest.fn()) { - const branch: ShortLivingBranch = { - isMain: false, - name: 'feature', - mergeBranch: 'master', - type: BranchType.SHORT - }; +function shallowRender( + branchLike: BranchLike, + onDelete: () => void = jest.fn(), + onClose: () => void = jest.fn() +) { const wrapper = shallow( - <DeleteBranchModal branch={branch} component="foo" onClose={onClose} onDelete={onDelete} /> + <DeleteBranchModal + branchLike={branchLike} + component="foo" + onClose={onClose} + onDelete={onDelete} + /> ); (wrapper.instance() as any).mounted = true; return wrapper; diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx index 0e2a0bf65a7..5c19f9f989f 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx @@ -53,7 +53,10 @@ it('opens form', () => { it('fetches setting value on mount', () => { shallow(<LongBranchesPattern project="project" />); - expect(getValues).lastCalledWith('sonar.branch.longLivedBranches.regex', 'project'); + expect(getValues).lastCalledWith({ + keys: 'sonar.branch.longLivedBranches.regex', + component: 'project' + }); }); it('fetches new setting value after change', () => { diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx index 6a107645fc0..4989daa203d 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx @@ -76,7 +76,11 @@ it('resets value', async () => { expect(wrapper).toMatchSnapshot(); click(wrapper.find('Button')); - expect(resetSettingValue).toBeCalledWith('foo', 'project', undefined); + expect(resetSettingValue).toBeCalledWith({ + keys: 'foo', + component: 'project', + branch: undefined + }); await new Promise(setImmediate); expect(onChange).toBeCalled(); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap index 1c14e27cdbe..a9d71179c0c 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap @@ -29,16 +29,27 @@ exports[`renders sorted list of branches 1`] = ` values={ Object { "days": "100", - "settings": <Link - onlyActiveOnIndex={false} - style={Object {}} - to="/admin/settings" - > - settings.page - </Link>, } } /> + <React.Fragment> + <br /> + <FormattedMessage + defaultMessage="project_branches.page.life_time.admin" + id="project_branches.page.life_time.admin" + values={ + Object { + "settings": <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/admin/settings" + > + settings.page + </Link>, + } + } + /> + </React.Fragment> </p> </header> <div @@ -71,31 +82,45 @@ exports[`renders sorted list of branches 1`] = ` </thead> <tbody> <BranchRow - branch={ + branchLike={ Object { "isMain": true, "name": "master", } } component="foo" - key="master" + isOrphan={false} + key="branch-master" + onChange={[MockFunction]} + /> + <BranchRow + branchLike={ + Object { + "base": "master", + "branch": "feature", + "key": "1234", + "title": "Feature PR", + } + } + component="foo" + key="pull-request-1234" onChange={[MockFunction]} /> <BranchRow - branch={ + branchLike={ Object { "isMain": false, "mergeBranch": "master", - "name": "branch-1.0", + "name": "feature", "type": "SHORT", } } component="foo" - key="branch-1.0" + key="branch-feature" onChange={[MockFunction]} /> <BranchRow - branch={ + branchLike={ Object { "isMain": false, "name": "branch-1.0", @@ -103,7 +128,8 @@ exports[`renders sorted list of branches 1`] = ` } } component="foo" - key="branch-1.0" + isOrphan={false} + key="branch-branch-1.0" onChange={[MockFunction]} /> </tbody> diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap index 5a28f352855..d7e94ca3b2e 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap @@ -4,7 +4,7 @@ exports[`renders main branch 1`] = ` <tr> <td> <BranchIcon - branch={ + branchLike={ Object { "isMain": true, "name": "master", @@ -23,7 +23,7 @@ exports[`renders main branch 1`] = ` className="thin nowrap text-right" > <BranchStatus - branch={ + branchLike={ Object { "isMain": true, "name": "master", @@ -51,11 +51,62 @@ exports[`renders main branch 1`] = ` </tr> `; +exports[`renders pull request 1`] = ` +<tr> + <td> + <BranchIcon + branchLike={ + Object { + "base": "master", + "branch": "feature", + "key": "1234", + "title": "Feature PR", + } + } + className="little-spacer-right big-spacer-left" + /> + 1234 – Feature PR + </td> + <td + className="thin nowrap text-right" + > + <BranchStatus + branchLike={ + Object { + "base": "master", + "branch": "feature", + "key": "1234", + "title": "Feature PR", + } + } + /> + </td> + <td + className="thin nowrap text-right" + /> + <td + className="thin nowrap text-right" + > + <ActionsDropdown + className="ig-spacer-left" + > + <ActionsDropdownItem + className="js-delete" + destructive={true} + onClick={[Function]} + > + branches.pull_request.delete + </ActionsDropdownItem> + </ActionsDropdown> + </td> +</tr> +`; + exports[`renders short-living branch 1`] = ` <tr> <td> <BranchIcon - branch={ + branchLike={ Object { "analysisDate": "2017-09-27T00:05:19+0000", "isMain": false, @@ -72,7 +123,7 @@ exports[`renders short-living branch 1`] = ` className="thin nowrap text-right" > <BranchStatus - branch={ + branchLike={ Object { "analysisDate": "2017-09-27T00:05:19+0000", "isMain": false, 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 505f5276139..733d95af43f 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,7 +18,6 @@ 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 afb4e501021..b6e9c87e444 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,7 +18,6 @@ exports[`renders 1`] = ` "link": Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "foo", }, }, diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx index b43fd2d96fa..2c58557ac4f 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx @@ -57,8 +57,8 @@ export default class ProjectRowActions extends React.PureComponent<Props, State> // call `getComponentNavigation` to check if user has the "Administer" permission // call `getComponentShow` to check if user has the "Browse" permission Promise.all([ - getComponentNavigation(this.props.project.key), - getComponentShow(this.props.project.key) + getComponentNavigation({ componentKey: this.props.project.key }), + getComponentShow({ component: this.props.project.key }) ]).then( ([navResponse]) => { if (this.mounted) { 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 deec1e2e7fd..b0b662adaae 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 @@ -385,7 +385,6 @@ exports[`creates project 4`] = ` Object { "pathname": "/dashboard", "query": Object { - "branch": undefined, "id": "name", }, } diff --git a/server/sonar-web/src/main/js/apps/settings/store/actions.js b/server/sonar-web/src/main/js/apps/settings/store/actions.js index 7865fd56313..dcbf19f63dd 100644 --- a/server/sonar-web/src/main/js/apps/settings/store/actions.js +++ b/server/sonar-web/src/main/js/apps/settings/store/actions.js @@ -50,10 +50,10 @@ export const fetchSettings = componentKey => dispatch => { ); }; -export const fetchValues = (keys, componentKey) => dispatch => - getValues(keys, componentKey).then( +export const fetchValues = (keys, component) => dispatch => + getValues({ keys, component }).then( settings => { - dispatch(receiveValues(settings, componentKey)); + dispatch(receiveValues(settings, component)); dispatch(closeAllGlobalMessages()); }, () => {} @@ -73,7 +73,7 @@ export const saveValue = (key, componentKey) => (dispatch, getState) => { } return setSettingValue(definition, value, componentKey) - .then(() => getValues(key, componentKey)) + .then(() => getValues({ keys: key, component: componentKey })) .then(values => { dispatch(receiveValues(values, componentKey)); dispatch(cancelChange(key)); @@ -90,8 +90,8 @@ export const saveValue = (key, componentKey) => (dispatch, getState) => { export const resetValue = (key, componentKey) => dispatch => { dispatch(startLoading(key)); - return resetSettingValue(key, componentKey) - .then(() => getValues(key, componentKey)) + return resetSettingValue({ keys: key, component: componentKey }) + .then(() => getValues({ keys: key, component: componentKey })) .then(values => { if (values.length > 0) { dispatch(receiveValues(values, componentKey)); |