Browse Source

SONAR-12635 Reorganize the branch & PR administration page

tags/8.1.0.31237
Philippe Perrin 4 years ago
parent
commit
fe9dd29337
31 changed files with 1805 additions and 636 deletions
  1. 1
    1
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
  2. 3
    3
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
  3. 22
    152
      server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx
  4. 77
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeRowRenderer.tsx
  5. 66
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTableRenderer.tsx
  6. 138
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx
  7. 0
    153
      server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx
  8. 8
    6
      server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx
  9. 88
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformation.tsx
  10. 61
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx
  11. 3
    3
      server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx
  12. 26
    36
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx
  13. 62
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchLikeRowRenderer-test.tsx
  14. 71
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchLikeTableRenderer-test.tsx
  15. 126
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchLikeTabs-test.tsx
  16. 0
    77
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx
  17. 7
    2
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx
  18. 43
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LifetimeInformation-test.tsx
  19. 51
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LifetimeInformationRenderer-test.tsx
  20. 9
    3
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx
  21. 96
    153
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap
  22. 80
    18
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeRowRenderer-test.tsx.snap
  23. 368
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTableRenderer-test.tsx.snap
  24. 274
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTabs-test.tsx.snap
  25. 6
    6
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap
  26. 16
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/LifetimeInformation-test.tsx.snap
  27. 65
    0
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/LifetimeInformationRenderer-test.tsx.snap
  28. 6
    6
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap
  29. 1
    1
      server/sonar-web/src/main/js/apps/projectBranches/routes.ts
  30. 19
    7
      server/sonar-web/src/main/js/helpers/mocks/branch-pull-request.tsx
  31. 12
    9
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

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

@@ -267,7 +267,7 @@ export class ComponentNavMenu extends React.PureComponent<Props> {
<Link
activeClassName="active"
to={{ pathname: '/project/branches', query: { id: this.props.component.key } }}>
{translate('project_branches.page')}
{translate('project_branch_pull_request.page')}
</Link>
</li>
);

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

@@ -161,7 +161,7 @@ exports[`should work for all qualifiers 1`] = `
}
}
>
project_branches.page
project_branch_pull_request.page
</Link>
</li>
<li>
@@ -902,7 +902,7 @@ exports[`should work with extensions 2`] = `
}
}
>
project_branches.page
project_branch_pull_request.page
</Link>
</li>
<li>
@@ -1071,7 +1071,7 @@ exports[`should work with multiple extensions 2`] = `
}
}
>
project_branches.page
project_branch_pull_request.page
</Link>
</li>
<li>

+ 22
- 152
server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx View File

@@ -18,163 +18,33 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { formatMeasure } from 'sonar-ui-common/helpers/measures';
import { getValues } from '../../../api/settings';
import {
getBranchLikeKey,
isPullRequest,
isShortLivingBranch,
sortBranchesAsTree
} from '../../../helpers/branches';
import BranchRow from './BranchRow';
import BranchLikeTabs from './BranchLikeTabs';
import LifetimeInformation from './LifetimeInformation';

interface Props {
export interface AppProps {
branchLikes: T.BranchLike[];
canAdmin?: boolean;
component: { key: string };
component: T.Component;
onBranchesChange: () => void;
}

interface State {
branchLifeTime?: string;
loading: boolean;
export function App(props: AppProps) {
const { branchLikes, component, onBranchesChange } = props;

return (
<div className="page page-limited">
<header className="page-header">
<h1>{translate('project_branch_pull_request.page')}</h1>
<LifetimeInformation />
</header>

<BranchLikeTabs
branchLikes={branchLikes}
component={component}
onBranchesChange={onBranchesChange}
/>
</div>
);
}

const BRANCH_LIFETIME_SETTING = 'sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches';

export default class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: true };

componentDidMount() {
this.mounted = true;
this.fetchPurgeSetting();
}

componentWillUnmount() {
this.mounted = false;
}

fetchPurgeSetting() {
this.setState({ loading: true });
getValues({ keys: BRANCH_LIFETIME_SETTING }).then(
settings => {
if (this.mounted) {
this.setState({
loading: false,
branchLifeTime: settings.length > 0 ? settings[0].value : undefined
});
}
},
() => {
this.setState({ loading: false });
}
);
}

isOrphan = (branchLike: T.BranchLike) => {
return (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && branchLike.isOrphan;
};

renderBranchLifeTime() {
const { branchLifeTime } = this.state;
if (!branchLifeTime) {
return null;
}

return (
<p className="page-description">
<FormattedMessage
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 { branchLikes, component, onBranchesChange } = this.props;

if (this.state.loading) {
return (
<div className="page page-limited">
<header className="page-header">
<h1 className="page-title">{translate('project_branches.page')}</h1>
</header>
<i className="spinner" />
</div>
);
}

return (
<div className="page page-limited">
<header className="page-header">
<h1 className="page-title">{translate('project_branches.page')}</h1>
<p className="page-description">{translate('project_branches.page.description')}</p>
{this.renderBranchLifeTime()}
</header>

<div className="boxed-group boxed-group-inner">
<table className="data zebra zebra-hover">
<thead>
<tr>
<th>{translate('branch')}</th>
<th className="thin nowrap">{translate('status')}</th>
<th className="thin nowrap text-right big-spacer-left">
{translate('branches.last_analysis_date')}
</th>
<th className="thin nowrap text-right">{translate('actions')}</th>
</tr>
</thead>
<tbody>
{sortBranchesAsTree(branchLikes).map((branchLike, index) => {
const isOrphan = this.isOrphan(branchLike);
const previous = index > 0 ? branchLikes[index - 1] : undefined;
const isPreviousOrphan = previous !== undefined && this.isOrphan(previous);
const showOrphanHeader = isOrphan && !isPreviousOrphan;
return (
<React.Fragment key={getBranchLikeKey(branchLike)}>
{showOrphanHeader && (
<tr>
<td colSpan={4}>
<div className="display-inline-block text-middle">
{translate('branches.orphan_branches')}
</div>
<HelpTooltip
className="spacer-left"
overlay={translate('branches.orphan_branches.tooltip')}
/>
</td>
</tr>
)}
<BranchRow
branchLike={branchLike}
component={component.key}
isOrphan={isOrphan}
onChange={onBranchesChange}
/>
</React.Fragment>
);
})}
</tbody>
</table>
</div>
</div>
);
}
}
export default React.memo(App);

+ 77
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeRowRenderer.tsx View File

@@ -0,0 +1,77 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 ActionsDropdown, {
ActionsDropdownItem
} from 'sonar-ui-common/components/controls/ActionsDropdown';
import { translate } from 'sonar-ui-common/helpers/l10n';
import BranchStatus from '../../../components/common/BranchStatus';
import BranchIcon from '../../../components/icons-components/BranchIcon';
import DateFromNow from '../../../components/intl/DateFromNow';
import { getBranchLikeDisplayName, isMainBranch, isPullRequest } from '../../../helpers/branches';

export interface BranchLikeRowRendererProps {
branchLike: T.BranchLike;
component: T.Component;
onDelete: () => void;
onRename: () => void;
}

export function BranchLikeRowRenderer(props: BranchLikeRowRendererProps) {
const { branchLike, component, onDelete, onRename } = props;

return (
<tr>
<td>
<BranchIcon branchLike={branchLike} className="little-spacer-right" />
{getBranchLikeDisplayName(branchLike)}
{isMainBranch(branchLike) && (
<div className="badge spacer-left">{translate('branches.main_branch')}</div>
)}
</td>
<td className="thin nowrap">
<BranchStatus branchLike={branchLike} component={component.key} />
</td>
<td className="thin nowrap text-right big-spacer-left">
{branchLike.analysisDate && <DateFromNow date={branchLike.analysisDate} />}
</td>
<td className="thin nowrap text-right">
<ActionsDropdown className="big-spacer-left">
{isMainBranch(branchLike) ? (
<ActionsDropdownItem className="js-rename" onClick={onRename}>
{translate('project_branch_pull_request.branch.rename')}
</ActionsDropdownItem>
) : (
<ActionsDropdownItem className="js-delete" destructive={true} onClick={onDelete}>
{translate(
isPullRequest(branchLike)
? 'project_branch_pull_request.pull_request.delete'
: 'project_branch_pull_request.branch.delete'
)}
</ActionsDropdownItem>
)}
</ActionsDropdown>
</td>
</tr>
);
}

export default React.memo(BranchLikeRowRenderer);

+ 66
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTableRenderer.tsx View File

@@ -0,0 +1,66 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { translate } from 'sonar-ui-common/helpers/l10n';
import { getBranchLikeKey } from '../../../helpers/branches';
import BranchLikeRowRenderer from './BranchLikeRowRenderer';

export interface BranchLikeTableRendererProps {
component: T.Component;
tableTitle: string;
branchLikes: T.BranchLike[];
onDelete: (branchLike: T.BranchLike) => void;
onRename: (branchLike: T.BranchLike) => void;
}

export function BranchLikeTableRenderer(props: BranchLikeTableRendererProps) {
const { branchLikes, component, onDelete, onRename, tableTitle } = props;

return (
<div className="boxed-group boxed-group-inner">
<table className="data zebra zebra-hover">
<thead>
<tr>
<th>{tableTitle}</th>
<th className="thin nowrap">{translate('status')}</th>
<th className="thin nowrap text-right big-spacer-left">
{translate('branches.last_analysis_date')}
</th>
<th className="thin nowrap text-right">{translate('actions')}</th>
</tr>
</thead>
<tbody>
{branchLikes.map(branchLike => (
<BranchLikeRowRenderer
branchLike={branchLike}
component={component}
key={getBranchLikeKey(branchLike)}
onDelete={() => onDelete(branchLike)}
onRename={() => onRename(branchLike)}
/>
))}
</tbody>
</table>
</div>
);
}

export default React.memo(BranchLikeTableRenderer);

+ 138
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx View File

@@ -0,0 +1,138 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
import PullRequestIcon from 'sonar-ui-common/components/icons/PullRequestIcon';
import ShortLivingBranchIcon from 'sonar-ui-common/components/icons/ShortLivingBranchIcon';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { isBranch, isMainBranch, isPullRequest, sortBranches } from '../../../helpers/branches';
import BranchLikeTableRenderer from './BranchLikeTableRenderer';
import DeleteBranchModal from './DeleteBranchModal';
import RenameBranchModal from './RenameBranchModal';

interface Props {
branchLikes: T.BranchLike[];
component: T.Component;
onBranchesChange: () => void;
}

interface State {
currentTab: Tabs;
deleting?: T.BranchLike;
renaming?: T.BranchLike;
}

export enum Tabs {
Branch,
PullRequest
}

const TABS = [
{
key: Tabs.Branch,
label: (
<>
<ShortLivingBranchIcon />
<span className="spacer-left">
{translate('project_branch_pull_request.tabs.branches')}
</span>
</>
)
},
{
key: Tabs.PullRequest,
label: (
<>
<PullRequestIcon />
<span className="spacer-left">
{translate('project_branch_pull_request.tabs.pull_requests')}
</span>
</>
)
}
];

export default class BranchLikeTabs extends React.PureComponent<Props, State> {
state: State = { currentTab: Tabs.Branch };

onTabSelect = (currentTab: Tabs) => {
this.setState({ currentTab });
};

onDeleteBranchLike = (branchLike: T.BranchLike) => this.setState({ deleting: branchLike });

onRenameBranchLike = (branchLike: T.BranchLike) => this.setState({ renaming: branchLike });

onClose = () => this.setState({ deleting: undefined, renaming: undefined });

onModalActionFulfilled = () => {
this.onClose();
this.props.onBranchesChange();
};

render() {
const { branchLikes, component } = this.props;
const { currentTab, deleting, renaming } = this.state;

let tableTitle = '';
let branchLikesToDisplay: T.BranchLike[] = [];

if (currentTab === Tabs.Branch) {
tableTitle = translate('project_branch_pull_request.table.branch');
branchLikesToDisplay = sortBranches(branchLikes.filter(isBranch));
} else if (currentTab === Tabs.PullRequest) {
tableTitle = translate('project_branch_pull_request.table.pull_request');
branchLikesToDisplay = branchLikes.filter(isPullRequest);
}

return (
<>
<BoxedTabs onSelect={this.onTabSelect} selected={currentTab} tabs={TABS} />

<BranchLikeTableRenderer
branchLikes={branchLikesToDisplay}
component={component}
onDelete={this.onDeleteBranchLike}
onRename={this.onRenameBranchLike}
tableTitle={tableTitle}
/>

{deleting && (
<DeleteBranchModal
branchLike={deleting}
component={component}
onClose={this.onClose}
onDelete={this.onModalActionFulfilled}
/>
)}

{renaming && isMainBranch(renaming) && (
<RenameBranchModal
branch={renaming}
component={component}
onClose={this.onClose}
onRename={this.onModalActionFulfilled}
/>
)}
</>
);
}
}

+ 0
- 153
server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx View File

@@ -1,153 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 classNames from 'classnames';
import * as React from 'react';
import ActionsDropdown, {
ActionsDropdownItem
} from 'sonar-ui-common/components/controls/ActionsDropdown';
import { translate } from 'sonar-ui-common/helpers/l10n';
import BranchStatus from '../../../components/common/BranchStatus';
import BranchIcon from '../../../components/icons-components/BranchIcon';
import DateFromNow from '../../../components/intl/DateFromNow';
import {
getBranchLikeDisplayName,
isMainBranch,
isPullRequest,
isShortLivingBranch
} from '../../../helpers/branches';
import DeleteBranchModal from './DeleteBranchModal';
import RenameBranchModal from './RenameBranchModal';

interface Props {
branchLike: T.BranchLike;
component: string;
isOrphan?: boolean;
onChange: () => void;
}

interface State {
deleting: boolean;
renaming: boolean;
}

export default class BranchRow extends React.PureComponent<Props, State> {
mounted = false;
state: State = { deleting: false, renaming: false };

componentDidMount() {
this.mounted = true;
}

componentWillUnmount() {
this.mounted = false;
}

handleDeleteClick = () => {
this.setState({ deleting: true });
};

handleDeletingStop = () => {
this.setState({ deleting: false });
};

handleRenameClick = () => {
this.setState({ renaming: true });
};

handleChange = () => {
if (this.mounted) {
this.setState({ deleting: false, renaming: false });
this.props.onChange();
}
};

handleRenamingStop = () => {
this.setState({ renaming: false });
};

renderActions() {
const { branchLike, component } = this.props;
return (
<td className="thin nowrap text-right">
<ActionsDropdown className="ig-spacer-left">
{isMainBranch(branchLike) ? (
<ActionsDropdownItem className="js-rename" onClick={this.handleRenameClick}>
{translate('branches.rename')}
</ActionsDropdownItem>
) : (
<ActionsDropdownItem
className="js-delete"
destructive={true}
onClick={this.handleDeleteClick}>
{translate(
isPullRequest(branchLike) ? 'branches.pull_request.delete' : 'branches.delete'
)}
</ActionsDropdownItem>
)}
</ActionsDropdown>

{this.state.deleting && (
<DeleteBranchModal
branchLike={branchLike}
component={component}
onClose={this.handleDeletingStop}
onDelete={this.handleChange}
/>
)}

{this.state.renaming && isMainBranch(branchLike) && (
<RenameBranchModal
branch={branchLike}
component={component}
onClose={this.handleRenamingStop}
onRename={this.handleChange}
/>
)}
</td>
);
}

render() {
const { branchLike, component, isOrphan } = this.props;
const indented = (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && !isOrphan;

return (
<tr>
<td>
<BranchIcon
branchLike={branchLike}
className={classNames('little-spacer-right', { 'big-spacer-left': indented })}
/>
{getBranchLikeDisplayName(branchLike)}
{isMainBranch(branchLike) && (
<div className="badge spacer-left">{translate('branches.main_branch')}</div>
)}
</td>
<td className="thin nowrap">
<BranchStatus branchLike={branchLike} component={component} />
</td>
<td className="thin nowrap text-right big-spacer-left">
{branchLike.analysisDate && <DateFromNow date={branchLike.analysisDate} />}
</td>
{this.renderActions()}
</tr>
);
}
}

+ 8
- 6
server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx View File

@@ -26,7 +26,7 @@ import { getBranchLikeDisplayName, isPullRequest } from '../../../helpers/branch

interface Props {
branchLike: T.BranchLike;
component: string;
component: T.Component;
onClose: () => void;
onDelete: () => void;
}
@@ -52,12 +52,12 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State>
this.setState({ loading: true });
const request = isPullRequest(this.props.branchLike)
? deletePullRequest({
project: this.props.component,
project: this.props.component.key,
pullRequest: this.props.branchLike.key
})
: deleteBranch({
branch: this.props.branchLike.name,
project: this.props.component
project: this.props.component.key
});
request.then(
() => {
@@ -77,7 +77,9 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State>
render() {
const { branchLike } = this.props;
const header = translate(
isPullRequest(branchLike) ? 'branches.pull_request.delete' : 'branches.delete'
isPullRequest(branchLike)
? 'project_branch_pull_request.pull_request.delete'
: 'project_branch_pull_request.branch.delete'
);

return (
@@ -89,8 +91,8 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State>
<div className="modal-body">
{translateWithParameters(
isPullRequest(branchLike)
? 'branches.pull_request.delete.are_you_sure'
: 'branches.delete.are_you_sure',
? 'project_branch_pull_request.pull_request.delete.are_you_sure'
: 'project_branch_pull_request.branch.delete.are_you_sure',
getBranchLikeDisplayName(branchLike)
)}
</div>

+ 88
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformation.tsx View File

@@ -0,0 +1,88 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { connect } from 'react-redux';
import { getValues } from '../../../api/settings';
import { getAppState, Store } from '../../../store/rootReducer';
import LifetimeInformationRenderer from './LifetimeInformationRenderer';

interface Props {
canAdmin?: boolean;
}

interface State {
branchAndPullRequestLifeTimeInDays?: string;
loading: boolean;
}

export const BRANCH_PULL_REQUEST_LIFETIME_SETTING =
'sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches';

export class LifetimeInformation extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: true };

componentDidMount() {
this.mounted = true;
this.fetchBranchAndPullRequestLifetimeSetting();
}

componentWillUnmount() {
this.mounted = false;
}

fetchBranchAndPullRequestLifetimeSetting() {
getValues({ keys: BRANCH_PULL_REQUEST_LIFETIME_SETTING }).then(
settings => {
if (this.mounted) {
this.setState({
loading: false,
branchAndPullRequestLifeTimeInDays: settings.length > 0 ? settings[0].value : undefined
});
}
},
() => {
if (this.mounted) {
this.setState({ loading: false });
}
}
);
}

render() {
const { canAdmin } = this.props;
const { branchAndPullRequestLifeTimeInDays, loading } = this.state;

return (
<LifetimeInformationRenderer
branchAndPullRequestLifeTimeInDays={branchAndPullRequestLifeTimeInDays}
canAdmin={canAdmin}
loading={loading}
/>
);
}
}

const mapStoreToProps = (state: Store) => ({
canAdmin: getAppState(state).canAdmin
});

export default connect(mapStoreToProps)(LifetimeInformation);

+ 61
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx View File

@@ -0,0 +1,61 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { formatMeasure } from 'sonar-ui-common/helpers/measures';

export interface LifetimeInformationRendererProps {
branchAndPullRequestLifeTimeInDays?: string;
canAdmin?: boolean;
loading: boolean;
}

export function LifetimeInformationRenderer(props: LifetimeInformationRendererProps) {
const { branchAndPullRequestLifeTimeInDays, canAdmin, loading } = props;

return (
<DeferredSpinner loading={loading}>
{branchAndPullRequestLifeTimeInDays && (
<p className="page-description">
<FormattedMessage
defaultMessage={translate('project_branch_pull_request.lifetime_information')}
id="project_branch_pull_request.lifetime_information"
values={{ days: formatMeasure(branchAndPullRequestLifeTimeInDays, 'INT') }}
/>
{canAdmin && (
<FormattedMessage
defaultMessage={translate('project_branch_pull_request.lifetime_information.admin')}
id="project_branch_pull_request.lifetime_information.admin"
values={{
settings: <Link to="/admin/settings">{translate('settings.page')}</Link>
}}
/>
)}
</p>
)}
</DeferredSpinner>
);
}

export default React.memo(LifetimeInformationRenderer);

+ 3
- 3
server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx View File

@@ -25,7 +25,7 @@ import { renameBranch } from '../../../api/branches';

interface Props {
branch: T.MainBranch;
component: string;
component: T.Component;
onClose: () => void;
onRename: () => void;
}
@@ -53,7 +53,7 @@ export default class RenameBranchModal extends React.PureComponent<Props, State>
return;
}
this.setState({ loading: true });
renameBranch(this.props.component, this.state.name).then(
renameBranch(this.props.component.key, this.state.name).then(
() => {
if (this.mounted) {
this.setState({ loading: false });
@@ -74,7 +74,7 @@ export default class RenameBranchModal extends React.PureComponent<Props, State>

render() {
const { branch } = this.props;
const header = translate('branches.rename');
const header = translate('project_branch_pull_request.branch.rename');
const submitDisabled =
this.state.loading || !this.state.name || this.state.name === branch.name;


+ 26
- 36
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx View File

@@ -17,48 +17,38 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { mount, shallow } from 'enzyme';

import { shallow } from 'enzyme';
import * as React from 'react';
import { getValues } from '../../../../api/settings';
import {
mockLongLivingBranch,
mockMainBranch,
mockPullRequest,
mockShortLivingBranch
} from '../../../../helpers/testMocks';
import App from '../App';
import { mockSetOfBranchAndPullRequest } from '../../../../helpers/mocks/branch-pull-request';
import { mockComponent } from '../../../../helpers/testMocks';
import { App, AppProps } from '../App';
import BranchLikeTabs from '../BranchLikeTabs';

it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

it('should properly notify that a branch or a pr has been changed/deleted', () => {
const onBranchesChange = jest.fn();
const wrapper = shallowRender({ onBranchesChange });

jest.mock('../../../../api/settings', () => ({
getValues: jest.fn(() => Promise.resolve([]))
}));
wrapper
.find(BranchLikeTabs)
.props()
.onBranchesChange();

beforeEach(() => {
jest.clearAllMocks();
expect(onBranchesChange).toHaveBeenCalled();
});

it('renders sorted list of branches', () => {
const branchLikes = [
mockMainBranch(),
mockLongLivingBranch(),
mockShortLivingBranch(),
mockPullRequest(),
mockShortLivingBranch({ mergeBranch: 'foobar', name: 'feature', isOrphan: true })
];
const wrapper = shallow(
function shallowRender(props?: Partial<AppProps>) {
return shallow(
<App
branchLikes={branchLikes}
canAdmin={true}
component={{ key: 'foo' }}
branchLikes={mockSetOfBranchAndPullRequest()}
component={mockComponent()}
onBranchesChange={jest.fn()}
{...props}
/>
);
wrapper.setState({ branchLifeTime: '100', loading: false });
expect(wrapper).toMatchSnapshot();
});

it('fetches branch life time setting on mount', () => {
mount(<App branchLikes={[]} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />);
expect(getValues).toBeCalledWith({
keys: 'sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches'
});
});
}

+ 62
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchLikeRowRenderer-test.tsx View File

@@ -0,0 +1,62 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
import * as React from 'react';
import {
mockComponent,
mockLongLivingBranch,
mockMainBranch,
mockPullRequest,
mockShortLivingBranch
} from '../../../../helpers/testMocks';
import { BranchLikeRowRenderer, BranchLikeRowRendererProps } from '../BranchLikeRowRenderer';

it('should render correctly for pull request', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

it('should render correctly for short lived branch', () => {
const wrapper = shallowRender({ branchLike: mockShortLivingBranch() });
expect(wrapper).toMatchSnapshot();
});

it('should render correctly for long lived branch', () => {
const wrapper = shallowRender({ branchLike: mockLongLivingBranch() });
expect(wrapper).toMatchSnapshot();
});

it('should render correctly for mai branch', () => {
const wrapper = shallowRender({ branchLike: mockMainBranch() });
expect(wrapper).toMatchSnapshot();
});

function shallowRender(props?: Partial<BranchLikeRowRendererProps>) {
return shallow(
<BranchLikeRowRenderer
branchLike={mockPullRequest()}
component={mockComponent()}
onDelete={jest.fn()}
onRename={jest.fn()}
{...props}
/>
);
}

+ 71
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchLikeTableRenderer-test.tsx View File

@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
import * as React from 'react';
import { mockSetOfBranchAndPullRequest } from '../../../../helpers/mocks/branch-pull-request';
import { mockComponent } from '../../../../helpers/testMocks';
import { BranchLikeRowRenderer } from '../BranchLikeRowRenderer';
import { BranchLikeTableRenderer, BranchLikeTableRendererProps } from '../BranchLikeTableRenderer';

it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

it('should properly propagate delete event', () => {
const onDelete = jest.fn();
const wrapper = shallowRender({ onDelete });

wrapper
.find(BranchLikeRowRenderer)
.first()
.props()
.onDelete();

expect(onDelete).toHaveBeenCalled();
});

it('should properly propagate rename event', () => {
const onDelete = jest.fn();
const onRename = jest.fn();
const wrapper = shallowRender({ onDelete, onRename });

wrapper
.find(BranchLikeRowRenderer)
.first()
.props()
.onRename();

expect(onRename).toHaveBeenCalled();
});

function shallowRender(props?: Partial<BranchLikeTableRendererProps>) {
return shallow(
<BranchLikeTableRenderer
branchLikes={mockSetOfBranchAndPullRequest()}
component={mockComponent()}
onDelete={jest.fn()}
onRename={jest.fn()}
tableTitle="tableTitle"
{...props}
/>
);
}

+ 126
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchLikeTabs-test.tsx View File

@@ -0,0 +1,126 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
import * as React from 'react';
import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
import { mockSetOfBranchAndPullRequest } from '../../../../helpers/mocks/branch-pull-request';
import { mockComponent, mockMainBranch, mockPullRequest } from '../../../../helpers/testMocks';
import { BranchLikeTableRenderer } from '../BranchLikeTableRenderer';
import BranchLikeTabs, { Tabs } from '../BranchLikeTabs';
import DeleteBranchModal from '../DeleteBranchModal';
import RenameBranchModal from '../RenameBranchModal';

it('should render all tabs correctly', () => {
const wrapper = shallowRender();

expect(wrapper.state().currentTab).toBe(Tabs.Branch);
expect(wrapper).toMatchSnapshot();

const onSelect = wrapper.find(BoxedTabs).prop('onSelect') as ((currentTab: Tabs) => void);
onSelect(Tabs.PullRequest);

expect(wrapper.state().currentTab).toBe(Tabs.PullRequest);
expect(wrapper).toMatchSnapshot();
});

it('should render deletion modal correctly', () => {
const onBranchesChange = jest.fn();
const wrapper = shallowRender({ onBranchesChange });

wrapper
.find(BranchLikeTableRenderer)
.props()
.onDelete(mockPullRequest());
expect(wrapper.state().deleting).toBeDefined();
expect(wrapper.find(DeleteBranchModal)).toMatchSnapshot();

wrapper
.find(DeleteBranchModal)
.props()
.onClose();
expect(wrapper.state().deleting).toBeUndefined();
expect(wrapper.find(DeleteBranchModal).exists()).toBeFalsy();

wrapper
.find(BranchLikeTableRenderer)
.props()
.onDelete(mockPullRequest());
wrapper
.find(DeleteBranchModal)
.props()
.onDelete();
expect(wrapper.state().deleting).toBeUndefined();
expect(wrapper.find(DeleteBranchModal).exists()).toBeFalsy();
expect(onBranchesChange).toHaveBeenCalled();
});

it('should render renaming modal correctly', () => {
const onBranchesChange = jest.fn();
const wrapper = shallowRender({ onBranchesChange });

wrapper
.find(BranchLikeTableRenderer)
.props()
.onRename(mockMainBranch());
expect(wrapper.state().renaming).toBeDefined();
expect(wrapper.find(RenameBranchModal)).toMatchSnapshot();

wrapper
.find(RenameBranchModal)
.props()
.onClose();
expect(wrapper.state().renaming).toBeUndefined();
expect(wrapper.find(RenameBranchModal).exists()).toBeFalsy();

wrapper
.find(BranchLikeTableRenderer)
.props()
.onRename(mockMainBranch());
wrapper
.find(RenameBranchModal)
.props()
.onRename();
expect(wrapper.state().renaming).toBeUndefined();
expect(wrapper.find(RenameBranchModal).exists()).toBeFalsy();
expect(onBranchesChange).toHaveBeenCalled();
});

it('should NOT render renaming modal for non-main branch', () => {
const wrapper = shallowRender();

wrapper
.find(BranchLikeTableRenderer)
.props()
.onRename(mockPullRequest());
expect(wrapper.state().renaming).toBeDefined();
expect(wrapper.find(RenameBranchModal).exists()).toBeFalsy();
});

function shallowRender(props: Partial<BranchLikeTabs['props']> = {}) {
return shallow<BranchLikeTabs>(
<BranchLikeTabs
branchLikes={mockSetOfBranchAndPullRequest()}
component={mockComponent()}
onBranchesChange={jest.fn()}
{...props}
/>
);
}

+ 0
- 77
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx View File

@@ -1,77 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
import * as React from 'react';
import { click } from 'sonar-ui-common/helpers/testUtils';
import { mockPullRequest, mockShortLivingBranch } from '../../../../helpers/testMocks';
import BranchRow from '../BranchRow';

const mainBranch: T.MainBranch = { isMain: true, name: 'master' };

const shortBranch = mockShortLivingBranch();

const pullRequest = mockPullRequest();

it('renders main branch', () => {
expect(shallowRender(mainBranch)).toMatchSnapshot();
});

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

click(wrapper.find('.js-rename'));
(wrapper.find('RenameBranchModal').prop('onRename') as Function)();
expect(onChange).toBeCalled();
});

it('deletes short-living branch', () => {
const onChange = jest.fn();
const wrapper = shallowRender(shortBranch, onChange);

click(wrapper.find('.js-delete'));
(wrapper.find('DeleteBranchModal').prop('onDelete') as Function)();
expect(onChange).toBeCalled();
});

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: T.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;
}

+ 7
- 2
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx View File

@@ -17,11 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import { click, doAsync, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { deleteBranch, deletePullRequest } from '../../../../api/branches';
import { mockPullRequest, mockShortLivingBranch } from '../../../../helpers/testMocks';
import {
mockComponent,
mockPullRequest,
mockShortLivingBranch
} from '../../../../helpers/testMocks';
import DeleteBranchModal from '../DeleteBranchModal';

jest.mock('../../../../api/branches', () => ({
@@ -101,7 +106,7 @@ function shallowRender(
const wrapper = shallow<DeleteBranchModal>(
<DeleteBranchModal
branchLike={branchLike}
component="foo"
component={mockComponent({ key: 'foo' })}
onClose={onClose}
onDelete={onDelete}
/>

+ 43
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LifetimeInformation-test.tsx View File

@@ -0,0 +1,43 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getValues } from '../../../../api/settings';
import { BRANCH_PULL_REQUEST_LIFETIME_SETTING, LifetimeInformation } from '../LifetimeInformation';

jest.mock('../../../../api/settings', () => ({
getValues: jest.fn().mockResolvedValue([{ value: '45' }])
}));

it('should render correctly', async () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot('initial_state');

await waitAndUpdate(wrapper);

expect(getValues).toHaveBeenCalledWith({ keys: BRANCH_PULL_REQUEST_LIFETIME_SETTING });
expect(wrapper).toMatchSnapshot('after_fetching_data');
});

function shallowRender(props: Partial<LifetimeInformation['props']> = {}) {
return shallow<LifetimeInformation>(<LifetimeInformation canAdmin={true} {...props} />);
}

+ 51
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LifetimeInformationRenderer-test.tsx View File

@@ -0,0 +1,51 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
import * as React from 'react';
import {
LifetimeInformationRenderer,
LifetimeInformationRendererProps
} from '../LifetimeInformationRenderer';

it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

it('should render correctly when user is admin', () => {
const wrapper = shallowRender({ canAdmin: true });
expect(wrapper).toMatchSnapshot();
});

it('should render correctly if not lifetime has been fetch', () => {
const wrapper = shallowRender({ branchAndPullRequestLifeTimeInDays: undefined });
expect(wrapper).toMatchSnapshot();
});

function shallowRender(props?: Partial<LifetimeInformationRendererProps>) {
return shallow(
<LifetimeInformationRenderer
branchAndPullRequestLifeTimeInDays="30"
loading={true}
{...props}
/>
);
}

+ 9
- 3
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx View File

@@ -17,15 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/* eslint-disable import/first */
jest.mock('../../../../api/branches', () => ({ renameBranch: jest.fn() }));

import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import { change, click, doAsync, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { renameBranch } from '../../../../api/branches';
import { mockComponent } from '../../../../helpers/testMocks';
import RenameBranchModal from '../RenameBranchModal';

jest.mock('../../../../api/branches', () => ({ renameBranch: jest.fn() }));

beforeEach(() => {
(renameBranch as jest.Mock<any>).mockClear();
});
@@ -78,7 +79,12 @@ it('stops loading on WS error', async () => {
function shallowRender(onRename: () => void = jest.fn(), onClose: () => void = jest.fn()) {
const branch: T.MainBranch = { isMain: true, name: 'master' };
const wrapper = shallow<RenameBranchModal>(
<RenameBranchModal branch={branch} component="foo" onClose={onClose} onRename={onRename} />
<RenameBranchModal
branch={branch}
component={mockComponent({ key: 'foo' })}
onClose={onClose}
onRename={onRename}
/>
);
wrapper.instance().mounted = true;
return wrapper;

+ 96
- 153
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap View File

@@ -1,165 +1,108 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders sorted list of branches 1`] = `
exports[`should render correctly 1`] = `
<div
className="page page-limited"
>
<header
className="page-header"
>
<h1
className="page-title"
>
project_branches.page
<h1>
project_branch_pull_request.page
</h1>
<p
className="page-description"
>
project_branches.page.description
</p>
<p
className="page-description"
>
<FormattedMessage
defaultMessage="project_branches.page.life_time"
id="project_branches.page.life_time"
values={
Object {
"days": "100",
}
}
/>
<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>,
}
}
/>
</p>
<Connect(LifetimeInformation) />
</header>
<div
className="boxed-group boxed-group-inner"
>
<table
className="data zebra zebra-hover"
>
<thead>
<tr>
<th>
branch
</th>
<th
className="thin nowrap"
>
status
</th>
<th
className="thin nowrap text-right big-spacer-left"
>
branches.last_analysis_date
</th>
<th
className="thin nowrap text-right"
>
actions
</th>
</tr>
</thead>
<tbody>
<BranchRow
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": true,
"name": "master",
}
}
component="foo"
isOrphan={false}
onChange={[MockFunction]}
/>
<BranchRow
branchLike={
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "1001",
"target": "master",
"title": "Foo Bar feature",
}
}
component="foo"
onChange={[MockFunction]}
/>
<BranchRow
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"mergeBranch": "master",
"name": "feature/foo",
"type": "SHORT",
}
}
component="foo"
onChange={[MockFunction]}
/>
<BranchRow
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "branch-6.7",
"type": "LONG",
}
}
component="foo"
isOrphan={false}
onChange={[MockFunction]}
/>
<tr>
<td
colSpan={4}
>
<div
className="display-inline-block text-middle"
>
branches.orphan_branches
</div>
<HelpTooltip
className="spacer-left"
overlay="branches.orphan_branches.tooltip"
/>
</td>
</tr>
<BranchRow
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"isOrphan": true,
"mergeBranch": "foobar",
"name": "feature",
"type": "SHORT",
}
}
component="foo"
isOrphan={true}
onChange={[MockFunction]}
/>
</tbody>
</table>
</div>
<BranchLikeTabs
branchLikes={
Array [
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"mergeBranch": "master",
"name": "slb-1",
"type": "SHORT",
},
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "llb-1",
"type": "LONG",
},
Object {
"analysisDate": "2018-01-01",
"isMain": true,
"name": "master",
},
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "1",
"target": "master",
"title": "PR-1",
},
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"mergeBranch": "llb-1",
"name": "slb-2",
"type": "SHORT",
},
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "2",
"target": "master",
"title": "PR-2",
},
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "llb-3",
"type": "LONG",
},
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "llb-2",
"type": "LONG",
},
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"isOrphan": true,
"key": "2",
"target": "llb-100",
"title": "PR-2",
},
]
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
onBranchesChange={[MockFunction]}
/>
</div>
`;

server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap → server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeRowRenderer-test.tsx.snap View File

@@ -1,11 +1,68 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders main branch 1`] = `
exports[`should render correctly for long lived branch 1`] = `
<tr>
<td>
<BranchIcon
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "branch-6.7",
"type": "LONG",
}
}
className="little-spacer-right"
/>
branch-6.7
</td>
<td
className="thin nowrap"
>
<Connect(BranchStatus)
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "branch-6.7",
"type": "LONG",
}
}
component="my-project"
/>
</td>
<td
className="thin nowrap text-right big-spacer-left"
>
<DateFromNow
date="2018-01-01"
/>
</td>
<td
className="thin nowrap text-right"
>
<ActionsDropdown
className="big-spacer-left"
>
<ActionsDropdownItem
className="js-delete"
destructive={true}
onClick={[MockFunction]}
>
project_branch_pull_request.branch.delete
</ActionsDropdownItem>
</ActionsDropdown>
</td>
</tr>
`;

exports[`should render correctly for mai branch 1`] = `
<tr>
<td>
<BranchIcon
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": true,
"name": "master",
}
@@ -25,34 +82,39 @@ exports[`renders main branch 1`] = `
<Connect(BranchStatus)
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": true,
"name": "master",
}
}
component="foo"
component="my-project"
/>
</td>
<td
className="thin nowrap text-right big-spacer-left"
/>
>
<DateFromNow
date="2018-01-01"
/>
</td>
<td
className="thin nowrap text-right"
>
<ActionsDropdown
className="ig-spacer-left"
className="big-spacer-left"
>
<ActionsDropdownItem
className="js-rename"
onClick={[Function]}
onClick={[MockFunction]}
>
branches.rename
project_branch_pull_request.branch.rename
</ActionsDropdownItem>
</ActionsDropdown>
</td>
</tr>
`;

exports[`renders pull request 1`] = `
exports[`should render correctly for pull request 1`] = `
<tr>
<td>
<BranchIcon
@@ -66,7 +128,7 @@ exports[`renders pull request 1`] = `
"title": "Foo Bar feature",
}
}
className="little-spacer-right big-spacer-left"
className="little-spacer-right"
/>
1001 – Foo Bar feature
</td>
@@ -84,7 +146,7 @@ exports[`renders pull request 1`] = `
"title": "Foo Bar feature",
}
}
component="foo"
component="my-project"
/>
</td>
<td
@@ -98,21 +160,21 @@ exports[`renders pull request 1`] = `
className="thin nowrap text-right"
>
<ActionsDropdown
className="ig-spacer-left"
className="big-spacer-left"
>
<ActionsDropdownItem
className="js-delete"
destructive={true}
onClick={[Function]}
onClick={[MockFunction]}
>
branches.pull_request.delete
project_branch_pull_request.pull_request.delete
</ActionsDropdownItem>
</ActionsDropdown>
</td>
</tr>
`;

exports[`renders short-living branch 1`] = `
exports[`should render correctly for short lived branch 1`] = `
<tr>
<td>
<BranchIcon
@@ -125,7 +187,7 @@ exports[`renders short-living branch 1`] = `
"type": "SHORT",
}
}
className="little-spacer-right big-spacer-left"
className="little-spacer-right"
/>
feature/foo
</td>
@@ -142,7 +204,7 @@ exports[`renders short-living branch 1`] = `
"type": "SHORT",
}
}
component="foo"
component="my-project"
/>
</td>
<td
@@ -156,14 +218,14 @@ exports[`renders short-living branch 1`] = `
className="thin nowrap text-right"
>
<ActionsDropdown
className="ig-spacer-left"
className="big-spacer-left"
>
<ActionsDropdownItem
className="js-delete"
destructive={true}
onClick={[Function]}
onClick={[MockFunction]}
>
branches.delete
project_branch_pull_request.branch.delete
</ActionsDropdownItem>
</ActionsDropdown>
</td>

+ 368
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTableRenderer-test.tsx.snap View File

@@ -0,0 +1,368 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="boxed-group boxed-group-inner"
>
<table
className="data zebra zebra-hover"
>
<thead>
<tr>
<th>
tableTitle
</th>
<th
className="thin nowrap"
>
status
</th>
<th
className="thin nowrap text-right big-spacer-left"
>
branches.last_analysis_date
</th>
<th
className="thin nowrap text-right"
>
actions
</th>
</tr>
</thead>
<tbody>
<Memo(BranchLikeRowRenderer)
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"mergeBranch": "master",
"name": "slb-1",
"type": "SHORT",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
key="branch-slb-1"
onDelete={[Function]}
onRename={[Function]}
/>
<Memo(BranchLikeRowRenderer)
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "llb-1",
"type": "LONG",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
key="branch-llb-1"
onDelete={[Function]}
onRename={[Function]}
/>
<Memo(BranchLikeRowRenderer)
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": true,
"name": "master",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
key="branch-master"
onDelete={[Function]}
onRename={[Function]}
/>
<Memo(BranchLikeRowRenderer)
branchLike={
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "1",
"target": "master",
"title": "PR-1",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
key="pull-request-1"
onDelete={[Function]}
onRename={[Function]}
/>
<Memo(BranchLikeRowRenderer)
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"mergeBranch": "llb-1",
"name": "slb-2",
"type": "SHORT",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
key="branch-slb-2"
onDelete={[Function]}
onRename={[Function]}
/>
<Memo(BranchLikeRowRenderer)
branchLike={
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "2",
"target": "master",
"title": "PR-2",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
key="pull-request-2"
onDelete={[Function]}
onRename={[Function]}
/>
<Memo(BranchLikeRowRenderer)
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "llb-3",
"type": "LONG",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
key="branch-llb-3"
onDelete={[Function]}
onRename={[Function]}
/>
<Memo(BranchLikeRowRenderer)
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "llb-2",
"type": "LONG",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
key="branch-llb-2"
onDelete={[Function]}
onRename={[Function]}
/>
<Memo(BranchLikeRowRenderer)
branchLike={
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"isOrphan": true,
"key": "2",
"target": "llb-100",
"title": "PR-2",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
key="pull-request-2"
onDelete={[Function]}
onRename={[Function]}
/>
</tbody>
</table>
</div>
`;

+ 274
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTabs-test.tsx.snap View File

@@ -0,0 +1,274 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render all tabs correctly 1`] = `
<Fragment>
<BoxedTabs
onSelect={[Function]}
selected={0}
tabs={
Array [
Object {
"key": 0,
"label": <React.Fragment>
<ShortLivingBranchIcon />
<span
className="spacer-left"
>
project_branch_pull_request.tabs.branches
</span>
</React.Fragment>,
},
Object {
"key": 1,
"label": <React.Fragment>
<PullRequestIcon />
<span
className="spacer-left"
>
project_branch_pull_request.tabs.pull_requests
</span>
</React.Fragment>,
},
]
}
/>
<Memo(BranchLikeTableRenderer)
branchLikes={
Array [
Object {
"analysisDate": "2018-01-01",
"isMain": true,
"name": "master",
},
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "llb-1",
"type": "LONG",
},
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "llb-2",
"type": "LONG",
},
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "llb-3",
"type": "LONG",
},
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"mergeBranch": "master",
"name": "slb-1",
"type": "SHORT",
},
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"mergeBranch": "llb-1",
"name": "slb-2",
"type": "SHORT",
},
]
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
onDelete={[Function]}
onRename={[Function]}
tableTitle="project_branch_pull_request.table.branch"
/>
</Fragment>
`;

exports[`should render all tabs correctly 2`] = `
<Fragment>
<BoxedTabs
onSelect={[Function]}
selected={1}
tabs={
Array [
Object {
"key": 0,
"label": <React.Fragment>
<ShortLivingBranchIcon />
<span
className="spacer-left"
>
project_branch_pull_request.tabs.branches
</span>
</React.Fragment>,
},
Object {
"key": 1,
"label": <React.Fragment>
<PullRequestIcon />
<span
className="spacer-left"
>
project_branch_pull_request.tabs.pull_requests
</span>
</React.Fragment>,
},
]
}
/>
<Memo(BranchLikeTableRenderer)
branchLikes={
Array [
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "1",
"target": "master",
"title": "PR-1",
},
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "2",
"target": "master",
"title": "PR-2",
},
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"isOrphan": true,
"key": "2",
"target": "llb-100",
"title": "PR-2",
},
]
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
onDelete={[Function]}
onRename={[Function]}
tableTitle="project_branch_pull_request.table.pull_request"
/>
</Fragment>
`;

exports[`should render deletion modal correctly 1`] = `
<DeleteBranchModal
branchLike={
Object {
"analysisDate": "2018-01-01",
"base": "master",
"branch": "feature/foo/bar",
"key": "1001",
"target": "master",
"title": "Foo Bar feature",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
onClose={[Function]}
onDelete={[Function]}
/>
`;

exports[`should render renaming modal correctly 1`] = `
<RenameBranchModal
branch={
Object {
"analysisDate": "2018-01-01",
"isMain": true,
"name": "master",
}
}
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
onClose={[Function]}
onRename={[Function]}
/>
`;

+ 6
- 6
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap View File

@@ -2,14 +2,14 @@

exports[`renders 1`] = `
<Modal
contentLabel="branches.delete"
contentLabel="project_branch_pull_request.branch.delete"
onRequestClose={[MockFunction]}
>
<header
className="modal-head"
>
<h2>
branches.delete
project_branch_pull_request.branch.delete
</h2>
</header>
<form
@@ -18,7 +18,7 @@ exports[`renders 1`] = `
<div
className="modal-body"
>
branches.delete.are_you_sure.feature/foo
project_branch_pull_request.branch.delete.are_you_sure.feature/foo
</div>
<footer
className="modal-foot"
@@ -41,14 +41,14 @@ exports[`renders 1`] = `

exports[`renders 2`] = `
<Modal
contentLabel="branches.delete"
contentLabel="project_branch_pull_request.branch.delete"
onRequestClose={[MockFunction]}
>
<header
className="modal-head"
>
<h2>
branches.delete
project_branch_pull_request.branch.delete
</h2>
</header>
<form
@@ -57,7 +57,7 @@ exports[`renders 2`] = `
<div
className="modal-body"
>
branches.delete.are_you_sure.feature/foo
project_branch_pull_request.branch.delete.are_you_sure.feature/foo
</div>
<footer
className="modal-foot"

+ 16
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/LifetimeInformation-test.tsx.snap View File

@@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: after_fetching_data 1`] = `
<Memo(LifetimeInformationRenderer)
branchAndPullRequestLifeTimeInDays="45"
canAdmin={true}
loading={false}
/>
`;

exports[`should render correctly: initial_state 1`] = `
<Memo(LifetimeInformationRenderer)
canAdmin={true}
loading={true}
/>
`;

+ 65
- 0
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/LifetimeInformationRenderer-test.tsx.snap View File

@@ -0,0 +1,65 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<DeferredSpinner
loading={true}
timeout={100}
>
<p
className="page-description"
>
<FormattedMessage
defaultMessage="project_branch_pull_request.lifetime_information"
id="project_branch_pull_request.lifetime_information"
values={
Object {
"days": "30",
}
}
/>
</p>
</DeferredSpinner>
`;

exports[`should render correctly if not lifetime has been fetch 1`] = `
<DeferredSpinner
loading={true}
timeout={100}
/>
`;

exports[`should render correctly when user is admin 1`] = `
<DeferredSpinner
loading={true}
timeout={100}
>
<p
className="page-description"
>
<FormattedMessage
defaultMessage="project_branch_pull_request.lifetime_information"
id="project_branch_pull_request.lifetime_information"
values={
Object {
"days": "30",
}
}
/>
<FormattedMessage
defaultMessage="project_branch_pull_request.lifetime_information.admin"
id="project_branch_pull_request.lifetime_information.admin"
values={
Object {
"settings": <Link
onlyActiveOnIndex={false}
style={Object {}}
to="/admin/settings"
>
settings.page
</Link>,
}
}
/>
</p>
</DeferredSpinner>
`;

+ 6
- 6
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap View File

@@ -2,7 +2,7 @@

exports[`renders 1`] = `
<Modal
contentLabel="branches.rename"
contentLabel="project_branch_pull_request.branch.rename"
onRequestClose={[MockFunction]}
size="small"
>
@@ -10,7 +10,7 @@ exports[`renders 1`] = `
className="modal-head"
>
<h2>
branches.rename
project_branch_pull_request.branch.rename
</h2>
</header>
<form
@@ -65,7 +65,7 @@ exports[`renders 1`] = `

exports[`renders 2`] = `
<Modal
contentLabel="branches.rename"
contentLabel="project_branch_pull_request.branch.rename"
onRequestClose={[MockFunction]}
size="small"
>
@@ -73,7 +73,7 @@ exports[`renders 2`] = `
className="modal-head"
>
<h2>
branches.rename
project_branch_pull_request.branch.rename
</h2>
</header>
<form
@@ -128,7 +128,7 @@ exports[`renders 2`] = `

exports[`renders 3`] = `
<Modal
contentLabel="branches.rename"
contentLabel="project_branch_pull_request.branch.rename"
onRequestClose={[MockFunction]}
size="small"
>
@@ -136,7 +136,7 @@ exports[`renders 3`] = `
className="modal-head"
>
<h2>
branches.rename
project_branch_pull_request.branch.rename
</h2>
</header>
<form

+ 1
- 1
server/sonar-web/src/main/js/apps/projectBranches/routes.ts View File

@@ -21,7 +21,7 @@ import { lazyLoad } from 'sonar-ui-common/components/lazyLoad';

const routes = [
{
indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) }
indexRoute: { component: lazyLoad(() => import('./components/App')) }
}
];


server/sonar-web/src/main/js/apps/projectBranches/components/AppContainer.ts → server/sonar-web/src/main/js/helpers/mocks/branch-pull-request.tsx View File

@@ -17,12 +17,24 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { connect } from 'react-redux';
import { getAppState, Store } from '../../../store/rootReducer';
import App from './App';

const mapStateToProps = (state: Store) => ({
canAdmin: getAppState(state).canAdmin
});
import {
mockLongLivingBranch,
mockMainBranch,
mockPullRequest,
mockShortLivingBranch
} from '../testMocks';

export default connect(mapStateToProps)(App);
export function mockSetOfBranchAndPullRequest(): T.BranchLike[] {
return [
mockShortLivingBranch({ name: 'slb-1' }),
mockLongLivingBranch({ name: 'llb-1' }),
mockMainBranch(),
mockPullRequest({ key: '1', title: 'PR-1' }),
mockShortLivingBranch({ name: 'slb-2', mergeBranch: 'llb-1' }),
mockPullRequest({ key: '2', title: 'PR-2' }),
mockLongLivingBranch({ name: 'llb-3' }),
mockLongLivingBranch({ name: 'llb-2' }),
mockPullRequest({ key: '2', title: 'PR-2', target: 'llb-100', isOrphan: true })
];
}

+ 12
- 9
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -534,10 +534,18 @@ portfolio_deletion.page.description=This portfolio and its sub-portfolios will b
application_deletion.page.description=Delete this application. Application projects will not be deleted. Projects referenced by this application will not be deleted. This operation cannot be undone.
application.branches.help=Easily create Application branches composed of the branches of projects in your application.
application.branches.link=Create Branch
project_branches.page=Branches & Pull Requests
project_branches.page.description=Use this page to manage project branches and pull requests.
project_branches.page.life_time=Short-lived branches and pull requests are permanently deleted after {days} days without analysis.
project_branches.page.life_time.admin=You can adjust this value globally in {settings}.
project_branch_pull_request.page=Branches & Pull Requests
project_branch_pull_request.lifetime_information=Branches and Pull Requests are permanently deleted after {days} days without analysis.
project_branch_pull_request.lifetime_information.admin=You can adjust this value globally in {settings}.
project_branch_pull_request.branch.rename=Rename branch
project_branch_pull_request.branch.delete=Delete branch
project_branch_pull_request.branch.delete.are_you_sure=Are you sure you want to delete branch "{0}"?
project_branch_pull_request.pull_request.delete=Delete Pull Request
project_branch_pull_request.pull_request.delete.are_you_sure=Are you sure you want to delete Pull Request "{0}"?
project_branch_pull_request.tabs.branches=Branches
project_branch_pull_request.tabs.pull_requests=Pull Requests
project_branch_pull_request.table.branch=Branch
project_branch_pull_request.table.pull_request=Pull Request
project_baseline.page=New Code Period
project_baseline.page.description=Use this page to manage the New Code Period of your project. {link}
project_baseline.page.description.link=Learn More
@@ -3180,11 +3188,6 @@ onboarding.tutorial.return_to_tutorial=Return to tutorial
# BRANCHES
#
#------------------------------------------------------------------------------
branches.delete=Delete Branch
branches.delete.are_you_sure=Are you sure you want to delete branch "{0}"?
branches.pull_request.delete=Delete Pull Request
branches.pull_request.delete.are_you_sure=Are you sure you want to delete pull request "{0}"?
branches.rename=Rename Branch
branches.manage=Manage branches
branches.orphan_branch=Orphan Branch
branches.orphan_branches=Orphan Branches & Pull Requests

Loading…
Cancel
Save