diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2017-09-22 13:56:45 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-22 13:56:45 +0200 |
commit | 94f500e0091bc438c306ee76454c5395d223fe30 (patch) | |
tree | 524b75969a1455f6410c5c6d14ccc9979e871c1b /server/sonar-web | |
parent | 93044147ce1178f75054ed27eb520c3ec8fb30dc (diff) | |
download | sonarqube-94f500e0091bc438c306ee76454c5395d223fe30.tar.gz sonarqube-94f500e0091bc438c306ee76454c5395d223fe30.zip |
apply branches feedback (#2535)
Diffstat (limited to 'server/sonar-web')
45 files changed, 735 insertions, 1144 deletions
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index 085b4be1979..a1d01845343 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -122,36 +122,36 @@ export default class ComponentContainer extends React.PureComponent<Props, State const { query } = this.props.location; const { branches, component, loading } = this.state; - if (loading) { - return <i className="spinner" />; - } - - if (!component) { + if (!loading && !component) { return <ComponentContainerNotFound />; } const branch = branches.find(b => (query.branch ? b.name === query.branch : b.isMain)); - const isFile = ['FIL', 'UTS'].includes(component.qualifier); - const configuration = component.configuration || {}; return ( <div> - {!isFile && ( + {component && + !['FIL', 'UTS'].includes(component.qualifier) && ( <ComponentNav branches={branches} currentBranch={branch} component={component} - conf={configuration} location={this.props.location} + onBranchesChange={this.handleBranchesChange} /> )} - {React.cloneElement(this.props.children, { - branch, - branches, - component: component, - onBranchesChange: this.handleBranchesChange, - onComponentChange: this.handleComponentChange - })} + {loading ? ( + <div className="page page-limited"> + <i className="spinner" /> + </div> + ) : ( + React.cloneElement(this.props.children, { + branch, + branches, + component, + onComponentChange: this.handleComponentChange + }) + )} </div> ); } diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx index 105931959f9..01ff6e3a4fa 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx @@ -106,20 +106,3 @@ it("doesn't load branches portfolio", () => { expect(wrapper.find(Inner).exists()).toBeTruthy(); }); }); - -it('updates branches on change', () => { - (getBranches as jest.Mock<any>).mockImplementation(() => Promise.resolve([])); - const wrapper = shallow( - <ComponentContainer location={{ query: { id: 'portfolioKey' } }}> - <Inner /> - </ComponentContainer> - ); - (wrapper.instance() as ComponentContainer).mounted = true; - wrapper.setState({ - branches: [{ isMain: true }], - component: { breadcrumbs: [{ key: 'projectKey', name: 'project', qualifier: 'TRK' }] }, - loading: false - }); - (wrapper.find(Inner).prop('onBranchesChange') as Function)(); - expect(getBranches).toBeCalledWith('projectKey'); -}); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css index 5f3958f35bd..347ea1a3df7 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css @@ -25,4 +25,14 @@ .navbar-context-meta-branch-menu-item { display: flex !important; justify-content: space-between; + align-items: center; +} + +.navbar-context-meta-branch-menu-item-name { + flex: 1; +} + +.navbar-context-meta-branch-menu-item-actions { + height: 12px; + margin-left: 32px; } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx index 1c7a748ecef..5d78833af2d 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx @@ -24,7 +24,7 @@ import ComponentNavMeta from './ComponentNavMeta'; import ComponentNavMenu from './ComponentNavMenu'; import ComponentNavBranch from './ComponentNavBranch'; import RecentHistory from '../../RecentHistory'; -import { Branch, Component, ComponentConfiguration } from '../../../types'; +import { Branch, Component } from '../../../types'; import ContextNavBar from '../../../../components/nav/ContextNavBar'; import { getTasksForComponent } from '../../../../api/ce'; import { STATUSES } from '../../../../apps/background-tasks/constants'; @@ -34,8 +34,8 @@ interface Props { branches: Branch[]; currentBranch?: Branch; component: Component; - conf: ComponentConfiguration; location: {}; + onBranchesChange: () => void; } interface State { @@ -102,17 +102,17 @@ export default class ComponentNav extends React.PureComponent<Props, State> { {this.props.currentBranch && ( <ComponentNavBranch branches={this.props.branches} + component={this.props.component} currentBranch={this.props.currentBranch} // to close dropdown on any location change location={this.props.location} - project={this.props.component} + onBranchesChange={this.props.onBranchesChange} /> )} <ComponentNavMeta branch={this.props.currentBranch} component={this.props.component} - conf={this.props.conf} incremental={this.state.incremental} isInProgress={this.state.isInProgress} isFailed={this.state.isFailed} @@ -122,7 +122,6 @@ export default class ComponentNav extends React.PureComponent<Props, State> { <ComponentNavMenu branch={this.props.currentBranch} component={this.props.component} - conf={this.props.conf} // to re-render selected menu item location={this.props.location} /> diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx index dd5ecaa3020..21010a1ec69 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx @@ -22,7 +22,6 @@ import * as classNames from 'classnames'; import * as PropTypes from 'prop-types'; import ComponentNavBranchesMenu from './ComponentNavBranchesMenu'; import SingleBranchHelperPopup from './SingleBranchHelperPopup'; -import NoBranchSupportPopup from './NoBranchSupportPopup'; import { Branch, Component } from '../../../types'; import BranchIcon from '../../../../components/icons-components/BranchIcon'; import { isShortLivingBranch } from '../../../../helpers/branches'; @@ -33,9 +32,10 @@ import Tooltip from '../../../../components/controls/Tooltip'; interface Props { branches: Branch[]; + component: Component; currentBranch: Branch; location?: any; - project: Component; + onBranchesChange: () => void; } interface State { @@ -53,8 +53,7 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State }; static contextTypes = { - branchesEnabled: PropTypes.bool.isRequired, - onSonarCloud: PropTypes.bool + branchesEnabled: PropTypes.bool.isRequired }; componentDidMount() { @@ -63,8 +62,8 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State componentWillReceiveProps(nextProps: Props) { if ( - nextProps.project !== this.props.project || - nextProps.currentBranch !== this.props.currentBranch || + nextProps.component !== this.props.component || + this.differentBranches(nextProps.currentBranch, this.props.currentBranch) || nextProps.location !== this.props.location ) { this.setState({ dropdownOpen: false, singleBranchPopupOpen: false }); @@ -75,11 +74,16 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State this.mounted = false; } + differentBranches(a: Branch, b: Branch) { + // if main branch changes name, we should not close the dropdown + return a.isMain && b.isMain ? false : a.name !== b.name; + } + handleClick = (event: React.SyntheticEvent<HTMLElement>) => { event.preventDefault(); event.stopPropagation(); event.currentTarget.blur(); - this.setState({ dropdownOpen: true }); + this.setState(state => ({ dropdownOpen: !state.dropdownOpen })); }; closeDropdown = () => { @@ -117,12 +121,15 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State }; renderDropdown = () => { + const { configuration } = this.props.component; return this.state.dropdownOpen ? ( <ComponentNavBranchesMenu branches={this.props.branches} + canAdmin={configuration && configuration.showSettings} + component={this.props.component} currentBranch={this.props.currentBranch} + onBranchesChange={this.props.onBranchesChange} onClose={this.closeDropdown} - project={this.props.project} /> ) : null; }; @@ -160,35 +167,11 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State </div> ); - renderNoBranchSupportPopup = () => ( - <div className="display-inline-block spacer-left"> - <a className="link-no-underline" href="#" onClick={this.handleNoBranchSupportClick}> - <HelpIcon fill="#cdcdcd" /> - </a> - <BubblePopupHelper - isOpen={this.state.noBranchSupportPopupOpen} - position="bottomleft" - popup={<NoBranchSupportPopup />} - togglePopup={this.toggleNoBranchSupportPopup} - /> - </div> - ); - render() { const { branches, currentBranch } = this.props; - if (this.context.onSonarCloud && !this.context.branchesEnabled) { - return null; - } - if (!this.context.branchesEnabled) { - return ( - <div className="navbar-context-branches"> - <BranchIcon branch={currentBranch} className="little-spacer-right" color="#cdcdcd" /> - <span className="note">{currentBranch.name}</span> - {this.renderNoBranchSupportPopup()} - </div> - ); + return null; } if (branches.length < 2) { diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx index 9f41411bbad..f4ea43b0bc6 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx @@ -28,14 +28,15 @@ import { } from '../../../../helpers/branches'; import { translate } from '../../../../helpers/l10n'; import { getProjectBranchUrl } from '../../../../helpers/urls'; -import { Link } from 'react-router'; import Tooltip from '../../../../components/controls/Tooltip'; interface Props { branches: Branch[]; + canAdmin?: boolean; + component: Component; currentBranch: Branch; + onBranchesChange: () => void; onClose: () => void; - project: Component; } interface State { @@ -65,7 +66,9 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, ); handleClickOutside = (event: Event) => { - if (!this.node || !this.node.contains(event.target as HTMLElement)) { + // do not close when rename or delete branch modal is open + const modal = document.querySelector('.modal'); + if (!modal && (!this.node || !this.node.contains(event.target as HTMLElement))) { this.props.onClose(); } }; @@ -125,11 +128,23 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, }; getSelected = () => { + if (this.state.selected) { + return this.state.selected; + } + const branches = this.getFilteredBranches(); - return this.state.selected || (branches.length > 0 && branches[0].name); + if (branches.find(b => b.name === this.props.currentBranch.name)) { + return this.props.currentBranch.name; + } + + if (branches.length > 0) { + return branches[0].name; + } + + return undefined; }; - getProjectBranchUrl = (branch: Branch) => getProjectBranchUrl(this.props.project.key, branch); + getProjectBranchUrl = (branch: Branch) => getProjectBranchUrl(this.props.component.key, branch); isSelected = (branch: Branch) => branch.name === this.getSelected(); @@ -179,8 +194,10 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, menu.push( <ComponentNavBranchesMenuItem branch={branch} - component={this.props.project} + canAdmin={this.props.canAdmin} + component={this.props.component} key={branch.name} + onBranchesChange={this.props.onBranchesChange} onSelect={this.handleSelect} selected={branch.name === selected} /> @@ -191,23 +208,10 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, }; render() { - const { project } = this.props; - const showManageLink = - project.qualifier === 'TRK' && project.configuration && project.configuration.showSettings; - return ( <div className="dropdown-menu dropdown-menu-shadow" ref={node => (this.node = node)}> {this.renderSearch()} {this.renderBranchesList()} - {showManageLink && ( - <div className="dropdown-bottom-hint text-right"> - <Link - className="text-muted" - to={{ pathname: '/project/branches', query: { id: project.key } }}> - {translate('branches.manage')} - </Link> - </div> - )} </div> ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx index fa49b53d2d8..a1e02a1aa8f 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx @@ -20,48 +20,125 @@ import * as React from 'react'; import { Link } from 'react-router'; import * as classNames from 'classnames'; +import DeleteBranchModal from './DeleteBranchModal'; +import RenameBranchModal from './RenameBranchModal'; import BranchStatus from '../../../../components/common/BranchStatus'; import { Branch, Component } from '../../../types'; import BranchIcon from '../../../../components/icons-components/BranchIcon'; +import ChangeIcon from '../../../../components/icons-components/ChangeIcon'; +import DeleteIcon from '../../../../components/icons-components/DeleteIcon'; import { isShortLivingBranch } from '../../../../helpers/branches'; import { translate } from '../../../../helpers/l10n'; import { getProjectBranchUrl } from '../../../../helpers/urls'; -interface Props { +export interface Props { branch: Branch; + canAdmin?: boolean; component: Component; + onBranchesChange: () => void; onSelect: (branch: Branch) => void; selected: boolean; } -export default function ComponentNavBranchesMenuItem({ branch, ...props }: Props) { - const handleMouseEnter = () => { - props.onSelect(branch); +interface State { + deleteBranchModal: boolean; + renameBranchModal: boolean; +} + +export default class ComponentNavBranchesMenuItem extends React.PureComponent<Props, State> { + state: State = { deleteBranchModal: false, renameBranchModal: false }; + + handleMouseEnter = () => { + this.props.onSelect(this.props.branch); + }; + + handleDeleteBranchClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { + event.preventDefault(); + event.currentTarget.blur(); + this.setState({ deleteBranchModal: true }); + }; + + handleDeleteBranchClose = () => { + this.setState({ deleteBranchModal: false }); + }; + + handleBranchDelete = () => { + this.props.onBranchesChange(); + this.setState({ deleteBranchModal: false }); + }; + + handleRenameBranchClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { + event.preventDefault(); + event.currentTarget.blur(); + this.setState({ renameBranchModal: true }); }; - return ( - <li key={branch.name} onMouseEnter={handleMouseEnter}> - <Link - className={classNames('navbar-context-meta-branch-menu-item', { - active: props.selected - })} - to={getProjectBranchUrl(props.component.key, branch)}> - <div> - <BranchIcon + handleRenameBranchClose = () => { + this.setState({ renameBranchModal: false }); + }; + + handleBranchRename = () => { + this.props.onBranchesChange(); + this.setState({ renameBranchModal: false }); + }; + + render() { + const { branch } = this.props; + return ( + <li key={branch.name} onMouseEnter={this.handleMouseEnter}> + <Link + className={classNames('navbar-context-meta-branch-menu-item', { + active: this.props.selected + })} + to={getProjectBranchUrl(this.props.component.key, branch)}> + <div className="navbar-context-meta-branch-menu-item-name"> + <BranchIcon + branch={branch} + className={classNames('little-spacer-right', { + 'big-spacer-left': isShortLivingBranch(branch) && !branch.isOrphan + })} + /> + {branch.name} + {branch.isMain && ( + <div className="outline-badge spacer-left">{translate('branches.main_branch')}</div> + )} + </div> + <div className="big-spacer-left note"> + <BranchStatus branch={branch} concise={true} /> + </div> + {this.props.canAdmin && ( + <div className="navbar-context-meta-branch-menu-item-actions"> + {branch.isMain ? ( + <button className="js-rename button-link" onClick={this.handleRenameBranchClick}> + <ChangeIcon /> + </button> + ) : ( + <button className="js-delete button-link" onClick={this.handleDeleteBranchClick}> + <DeleteIcon /> + </button> + )} + </div> + )} + </Link> + + {this.state.deleteBranchModal && ( + <DeleteBranchModal branch={branch} - className={classNames('little-spacer-right', { - 'big-spacer-left': isShortLivingBranch(branch) && !branch.isOrphan - })} + component={this.props.component.key} + onClose={this.handleDeleteBranchClose} + onDelete={this.handleBranchDelete} /> - {branch.name} - {branch.isMain && ( - <div className="outline-badge spacer-left">{translate('branches.main_branch')}</div> - )} - </div> - <div className="big-spacer-left note"> - <BranchStatus branch={branch} concise={true} /> - </div> - </Link> - </li> - ); + )} + + {this.state.renameBranchModal && ( + <RenameBranchModal + branch={branch} + component={this.props.component.key} + onClose={this.handleRenameBranchClose} + onRename={this.handleBranchRename} + /> + )} + </li> + ); + } } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx index 625043eb923..270829acee0 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { Link } from 'react-router'; import * as classNames from 'classnames'; import * as PropTypes from 'prop-types'; -import { Branch, Component, ComponentExtension, ComponentConfiguration } from '../../../types'; +import { Branch, Component, ComponentExtension } from '../../../types'; import NavBarTabs from '../../../../components/nav/NavBarTabs'; import { isShortLivingBranch, @@ -48,7 +48,6 @@ const SETTINGS_URLS = [ interface Props { branch?: Branch; component: Component; - conf: ComponentConfiguration; location?: any; } @@ -74,6 +73,10 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { return this.props.component.qualifier === 'APP'; } + getConfiguration() { + return this.props.component.configuration || {}; + } + renderDashboardLink() { if (this.props.branch && isShortLivingBranch(this.props.branch)) { return null; @@ -193,7 +196,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { renderAdministration() { const { branch } = this.props; - if (!this.props.conf.showSettings || (branch && isShortLivingBranch(branch))) { + if (!this.getConfiguration().showSettings || (branch && isShortLivingBranch(branch))) { return null; } @@ -209,7 +212,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { pathname: '/project/settings', query: { branch: getBranchName(branch), id: this.props.component.key } }}> - {translate('layout.settings')} + {translate('branches.branch_settings')} </Link> </li> ); @@ -238,7 +241,6 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { renderAdministrationLinks() { return [ this.renderSettingsLink(), - this.renderBranchesLink(), this.renderProfilesLink(), this.renderQualityGateLink(), this.renderCustomMeasuresLink(), @@ -252,7 +254,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { } renderSettingsLink() { - if (!this.props.conf.showSettings || this.isApplication() || this.isPortfolio()) { + if (!this.getConfiguration().showSettings || this.isApplication() || this.isPortfolio()) { return null; } return ( @@ -272,23 +274,8 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { ); } - renderBranchesLink() { - if (!this.context.branchesEnabled || !this.isProject() || !this.props.conf.showSettings) { - return null; - } - return ( - <li key="branches"> - <Link - to={{ pathname: '/project/branches', query: { id: this.props.component.key } }} - activeClassName="active"> - {translate('project_branches.page')} - </Link> - </li> - ); - } - renderProfilesLink() { - if (!this.props.conf.showQualityProfiles) { + if (!this.getConfiguration().showQualityProfiles) { return null; } return ( @@ -303,7 +290,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { } renderQualityGateLink() { - if (!this.props.conf.showQualityGates) { + if (!this.getConfiguration().showQualityGates) { return null; } return ( @@ -318,7 +305,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { } renderCustomMeasuresLink() { - if (!this.props.conf.showManualMeasures) { + if (!this.getConfiguration().showManualMeasures) { return null; } return ( @@ -333,7 +320,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { } renderLinksLink() { - if (!this.props.conf.showLinks) { + if (!this.getConfiguration().showLinks) { return null; } return ( @@ -348,7 +335,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { } renderPermissionsLink() { - if (!this.props.conf.showPermissions) { + if (!this.getConfiguration().showPermissions) { return null; } return ( @@ -363,7 +350,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { } renderBackgroundTasksLink() { - if (!this.props.conf.showBackgroundTasks) { + if (!this.getConfiguration().showBackgroundTasks) { return null; } return ( @@ -378,7 +365,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { } renderUpdateKeyLink() { - if (!this.props.conf.showUpdateKey) { + if (!this.getConfiguration().showUpdateKey) { return null; } return ( @@ -395,7 +382,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { renderDeletionLink() { const { qualifier } = this.props.component; - if (!this.props.conf.showSettings) { + if (!this.getConfiguration().showSettings) { return null; } @@ -426,7 +413,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { }; renderAdminExtensions() { - const extensions = this.props.conf.extensions || []; + const extensions = this.getConfiguration().extensions || []; return extensions.map(e => this.renderExtension(e, true)); } @@ -446,9 +433,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { {translate('more')} <i className="icon-dropdown" /> </a> - <ul className="dropdown-menu"> - {extensions.map(e => this.renderExtension(e, false))} - </ul> + <ul className="dropdown-menu">{extensions.map(e => this.renderExtension(e, false))}</ul> </li> ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx index 4c1e485000c..c5721aa4e24 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import IncrementalBadge from './IncrementalBadge'; import BranchStatus from '../../../../components/common/BranchStatus'; -import { Branch, Component, ComponentConfiguration } from '../../../types'; +import { Branch, Component } from '../../../types'; import Tooltip from '../../../../components/controls/Tooltip'; import PendingIcon from '../../../../components/icons-components/PendingIcon'; import DateTimeFormatter from '../../../../components/intl/DateTimeFormatter'; @@ -30,7 +30,6 @@ import { isShortLivingBranch } from '../../../../helpers/branches'; interface Props { branch?: Branch; component: Component; - conf: ComponentConfiguration; incremental?: boolean; isInProgress?: boolean; isFailed?: boolean; @@ -39,7 +38,8 @@ interface Props { export default function ComponentNavMeta(props: Props) { const metaList = []; - const canSeeBackgroundTasks = props.conf.showBackgroundTasks; + const canSeeBackgroundTasks = + props.component.configuration != undefined && props.component.configuration.showBackgroundTasks; const backgroundTasksUrl = (window as any).baseUrl + `/project/background_tasks?id=${encodeURIComponent(props.component.key)}`; diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx b/server/sonar-web/src/main/js/app/components/nav/component/DeleteBranchModal.tsx index 66d14ed260f..2273692a9a8 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/DeleteBranchModal.tsx @@ -19,9 +19,9 @@ */ import * as React from 'react'; import Modal from 'react-modal'; -import { deleteBranch } from '../../../api/branches'; -import { Branch } from '../../../app/types'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { deleteBranch } from '../../../../api/branches'; +import { Branch } from '../../../../app/types'; +import { translate, translateWithParameters } from '../../../../helpers/l10n'; interface Props { branch: Branch; @@ -66,6 +66,7 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State> handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); + event.stopPropagation(); this.props.onClose(); }; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx b/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx deleted file mode 100644 index db44804e214..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 BubblePopup from '../../../../components/common/BubblePopup'; -import { translate } from '../../../../helpers/l10n'; - -interface Props { - popupPosition?: any; -} - -export default function NoBranchSupportPopup(props: Props) { - return ( - <BubblePopup position={props.popupPosition} customClass="bubble-popup-bottom"> - <div className="abs-width-400"> - <h6 className="spacer-bottom">{translate('branches.no_support.header')}</h6> - <p className="big-spacer-bottom markdown">{translate('branches.no_support.header.text')}</p> - <p> - <a href="https://redirect.sonarsource.com/doc/branches.html" target="_blank"> - {translate('learn_more')} - </a> - <a - className="button spacer-left" - href="https://www.sonarsource.com/company/contact/" - target="_blank"> - {translate('buy_developer_pack')} - </a> - </p> - </div> - </BubblePopup> - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx b/server/sonar-web/src/main/js/app/components/nav/component/RenameBranchModal.tsx index 181fee72365..b17401c4e08 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/RenameBranchModal.tsx @@ -19,9 +19,9 @@ */ import * as React from 'react'; import Modal from 'react-modal'; -import { renameBranch } from '../../../api/branches'; -import { Branch } from '../../../app/types'; -import { translate } from '../../../helpers/l10n'; +import { renameBranch } from '../../../../api/branches'; +import { Branch } from '../../../../app/types'; +import { translate } from '../../../../helpers/l10n'; interface Props { branch: Branch; @@ -70,6 +70,7 @@ export default class RenameBranchModal extends React.PureComponent<Props, State> handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); + event.stopPropagation(); this.props.onClose(); }; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx index 6ae5fb1c768..6adc8751910 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx @@ -62,13 +62,15 @@ const component = { it('loads status', () => { getTasksForComponent.mockClear(); - mount(<ComponentNav branches={[]} component={component} conf={{}} location={{}} />); + mount( + <ComponentNav branches={[]} component={component} location={{}} onBranchesChange={jest.fn()} /> + ); expect(getTasksForComponent).toBeCalledWith('component'); }); it('renders', () => { const wrapper = shallow( - <ComponentNav branches={[]} component={component} conf={{}} location={{}} /> + <ComponentNav branches={[]} component={component} location={{}} onBranchesChange={jest.fn()} /> ); wrapper.setState({ incremental: true, diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx index 28931e0fc51..5a2343bb4c5 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx @@ -38,8 +38,9 @@ it('renders main branch', () => { shallow( <ComponentNavBranch branches={[branch, fooBranch]} + component={component} currentBranch={branch} - project={component} + onBranchesChange={jest.fn()} />, { context: { branchesEnabled: true } } ) @@ -59,8 +60,9 @@ it('renders short-living branch', () => { shallow( <ComponentNavBranch branches={[branch, fooBranch]} + component={component} currentBranch={branch} - project={component} + onBranchesChange={jest.fn()} />, { context: { branchesEnabled: true } } ) @@ -73,8 +75,9 @@ it('opens menu', () => { const wrapper = shallow( <ComponentNavBranch branches={[branch, fooBranch]} + component={component} currentBranch={branch} - project={component} + onBranchesChange={jest.fn()} />, { context: { branchesEnabled: true } } ); @@ -87,7 +90,12 @@ it('renders single branch popup', () => { const branch: MainBranch = { isMain: true, name: 'master' }; const component = {} as Component; const wrapper = shallow( - <ComponentNavBranch branches={[branch]} currentBranch={branch} project={component} />, + <ComponentNavBranch + branches={[branch]} + component={component} + currentBranch={branch} + onBranchesChange={jest.fn()} + />, { context: { branchesEnabled: true } } ); expect(wrapper).toMatchSnapshot(); @@ -96,29 +104,17 @@ it('renders single branch popup', () => { expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(true); }); -it('renders no branch support popup', () => { +it('renders nothing when no branch support', () => { const branch: MainBranch = { isMain: true, name: 'master' }; const component = {} as Component; const wrapper = shallow( <ComponentNavBranch branches={[branch, fooBranch]} + component={component} currentBranch={branch} - project={component} + onBranchesChange={jest.fn()} />, { context: { branchesEnabled: false } } ); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(false); - click(wrapper.find('a')); - expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(true); -}); - -it('renders nothing on SonarCloud without branch support', () => { - const branch: MainBranch = { isMain: true, name: 'master' }; - const component = {} as Component; - const wrapper = shallow( - <ComponentNavBranch branches={[branch]} currentBranch={branch} project={component} />, - { context: { branchesEnabled: false, onSonarCloud: true } } - ); expect(wrapper.type()).toBeNull(); }); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx index a4eb5bfbf82..13e251e059a 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx @@ -36,9 +36,10 @@ it('renders list', () => { shallow( <ComponentNavBranchesMenu branches={[mainBranch(), shortBranch('foo'), longBranch('bar'), shortBranch('baz', true)]} + component={project} currentBranch={mainBranch()} + onBranchesChange={jest.fn()} onClose={jest.fn()} - project={project} /> ) ).toMatchSnapshot(); @@ -48,9 +49,10 @@ it('searches', () => { const wrapper = shallow( <ComponentNavBranchesMenu branches={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]} + component={project} currentBranch={mainBranch()} + onBranchesChange={jest.fn()} onClose={jest.fn()} - project={project} /> ); wrapper.setState({ query: 'bar' }); @@ -61,9 +63,10 @@ it('selects next & previous', () => { const wrapper = shallow( <ComponentNavBranchesMenu branches={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]} + component={project} currentBranch={mainBranch()} + onBranchesChange={jest.fn()} onClose={jest.fn()} - project={project} /> ); elementKeydown(wrapper.find('input'), 40); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx index 2e1d56e2115..c8ff060fa56 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx @@ -19,63 +19,61 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import ComponentNavBranchesMenuItem from '../ComponentNavBranchesMenuItem'; +import ComponentNavBranchesMenuItem, { Props } from '../ComponentNavBranchesMenuItem'; import { BranchType, MainBranch, ShortLivingBranch, Component } from '../../../../types'; +import { click } from '../../../../../helpers/testUtils'; + +const component = { key: 'component' } as Component; + +const shortBranch: ShortLivingBranch = { + isMain: false, + mergeBranch: 'master', + name: 'foo', + status: { bugs: 1, codeSmells: 2, vulnerabilities: 3 }, + type: BranchType.SHORT +}; + +const mainBranch: MainBranch = { isMain: true, name: 'master' }; it('renders main branch', () => { - const component = { key: 'component' } as Component; - const mainBranch: MainBranch = { isMain: true, name: 'master' }; - expect( - shallow( - <ComponentNavBranchesMenuItem - branch={mainBranch} - component={component} - onSelect={jest.fn()} - selected={false} - /> - ) - ).toMatchSnapshot(); + expect(shallowRender({ branch: mainBranch })).toMatchSnapshot(); }); it('renders short-living branch', () => { - const component = { key: 'component' } as Component; - const shortBranch: ShortLivingBranch = { - isMain: false, - mergeBranch: 'master', - name: 'foo', - status: { bugs: 1, codeSmells: 2, vulnerabilities: 3 }, - type: BranchType.SHORT - }; - expect( - shallow( - <ComponentNavBranchesMenuItem - branch={shortBranch} - component={component} - onSelect={jest.fn()} - selected={false} - /> - ) - ).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot(); }); it('renders short-living orhpan branch', () => { - const component = { key: 'component' } as Component; - const shortBranch: ShortLivingBranch = { - isMain: false, - isOrphan: true, - mergeBranch: 'master', - name: 'foo', - status: { bugs: 1, codeSmells: 2, vulnerabilities: 3 }, - type: BranchType.SHORT - }; - expect( - shallow( - <ComponentNavBranchesMenuItem - branch={shortBranch} - component={component} - onSelect={jest.fn()} - selected={false} - /> - ) - ).toMatchSnapshot(); + expect(shallowRender({ branch: { ...shortBranch, isOrphan: true } })).toMatchSnapshot(); +}); + +it('renames main branch', () => { + const onBranchesChange = jest.fn(); + const wrapper = shallowRender({ branch: mainBranch, canAdmin: true, onBranchesChange }); + + click(wrapper.find('.js-rename')); + (wrapper.find('RenameBranchModal').prop('onRename') as Function)(); + expect(onBranchesChange).toBeCalled(); }); + +it('deletes short-living branch', () => { + const onBranchesChange = jest.fn(); + const wrapper = shallowRender({ canAdmin: true, onBranchesChange }); + + click(wrapper.find('.js-delete')); + (wrapper.find('DeleteBranchModal').prop('onDelete') as Function)(); + expect(onBranchesChange).toBeCalled(); +}); + +function shallowRender(props?: { [P in keyof Props]?: Props[P] }) { + return shallow( + <ComponentNavBranchesMenuItem + branch={shortBranch} + component={component} + onBranchesChange={jest.fn()} + onSelect={jest.fn()} + selected={false} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx index 2e4af3fb2ee..8405e371da7 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx @@ -20,55 +20,47 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import ComponentNavMenu from '../ComponentNavMenu'; -import { - Component, - ShortLivingBranch, - BranchType, - LongLivingBranch, - MainBranch -} from '../../../../types'; +import { ShortLivingBranch, BranchType, LongLivingBranch, MainBranch } from '../../../../types'; -const mainBranch: MainBranch = { - isMain: true, - name: 'master' +const mainBranch: MainBranch = { isMain: true, name: 'master' }; + +const baseComponent = { + breadcrumbs: [], + key: 'foo', + name: 'foo', + organization: 'org', + qualifier: 'TRK' }; it('should work with extensions', () => { const component = { - key: 'foo', - qualifier: 'TRK', + ...baseComponent, + configuration: { showSettings: true, extensions: [{ key: 'foo', name: 'Foo' }] }, extensions: [{ key: 'component-foo', name: 'ComponentFoo' }] }; - const conf = { - showSettings: true, - extensions: [{ key: 'foo', name: 'Foo' }] - }; expect( - shallow( - <ComponentNavMenu branch={mainBranch} component={component as Component} conf={conf} />, - { context: { branchesEnabled: true } } - ) + shallow(<ComponentNavMenu branch={mainBranch} component={component} />, { + context: { branchesEnabled: true } + }) ).toMatchSnapshot(); }); it('should work with multiple extensions', () => { const component = { - key: 'foo', - qualifier: 'TRK', + ...baseComponent, + configuration: { + showSettings: true, + extensions: [{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }] + }, extensions: [ { key: 'component-foo', name: 'ComponentFoo' }, { key: 'component-bar', name: 'ComponentBar' } ] }; - const conf = { - showSettings: true, - extensions: [{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }] - }; expect( - shallow( - <ComponentNavMenu branch={mainBranch} component={component as Component} conf={conf} />, - { context: { branchesEnabled: true } } - ) + shallow(<ComponentNavMenu branch={mainBranch} component={component} />, { + context: { branchesEnabled: true } + }) ).toMatchSnapshot(); }); @@ -79,10 +71,9 @@ it('should work for short-living branches', () => { name: 'feature', type: BranchType.SHORT }; - const component = { key: 'foo', qualifier: 'TRK' } as Component; - const conf = { showSettings: true }; + const component = { ...baseComponent, configuration: { showSettings: true } }; expect( - shallow(<ComponentNavMenu branch={branch} component={component} conf={conf} />, { + shallow(<ComponentNavMenu branch={branch} component={component} />, { context: { branchesEnabled: true } }) ).toMatchSnapshot(); @@ -90,12 +81,15 @@ it('should work for short-living branches', () => { it('should work for long-living branches', () => { const branch: LongLivingBranch = { isMain: false, name: 'release', type: BranchType.LONG }; - const component = { key: 'foo', qualifier: 'TRK' } as Component; [true, false].forEach(showSettings => expect( - shallow(<ComponentNavMenu branch={branch} component={component} conf={{ showSettings }} />, { - context: { branchesEnabled: true } - }) + shallow( + <ComponentNavMenu + branch={branch} + component={{ ...baseComponent, configuration: { showSettings } }} + />, + { context: { branchesEnabled: true } } + ) ).toMatchSnapshot() ); }); @@ -103,20 +97,12 @@ it('should work for long-living branches', () => { it('should work for all qualifiers', () => { ['TRK', 'BRC', 'VW', 'SVW', 'APP'].forEach(checkWithQualifier); expect.assertions(5); - function checkWithQualifier(qualifier: string) { - const component = { key: 'foo', qualifier } as Component; + const component = { ...baseComponent, configuration: { showSettings: true }, qualifier }; expect( - shallow( - <ComponentNavMenu - branch={mainBranch} - component={component} - conf={{ showSettings: true }} - />, - { - context: { branchesEnabled: true } - } - ) + shallow(<ComponentNavMenu branch={mainBranch} component={component} />, { + context: { branchesEnabled: true } + }) ).toMatchSnapshot(); } }); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx index 6949044c9ca..1e1d412852c 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx @@ -39,12 +39,7 @@ it('renders incremental badge', () => { function check(incremental: boolean) { expect( shallow( - <ComponentNavMeta - branch={{} as Branch} - component={component} - conf={{}} - incremental={incremental} - /> + <ComponentNavMeta branch={{} as Branch} component={component} incremental={incremental} /> ).find('IncrementalBadge') ).toHaveLength(incremental ? 1 : 0); } @@ -58,9 +53,7 @@ it('renders status of short-living branch', () => { status: { bugs: 0, codeSmells: 2, vulnerabilities: 3 }, type: BranchType.SHORT }; - expect( - shallow(<ComponentNavMeta branch={branch} component={component} conf={{}} />) - ).toMatchSnapshot(); + expect(shallow(<ComponentNavMeta branch={branch} component={component} />)).toMatchSnapshot(); }); it('renders meta for long-living branch', () => { @@ -70,7 +63,5 @@ it('renders meta for long-living branch', () => { status: { qualityGateStatus: 'OK' }, type: BranchType.LONG }; - expect( - shallow(<ComponentNavMeta branch={branch} component={component} conf={{}} />) - ).toMatchSnapshot(); + expect(shallow(<ComponentNavMeta branch={branch} component={component} />)).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/DeleteBranchModal-test.tsx index b2870587114..0df5a12fdbb 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/DeleteBranchModal-test.tsx @@ -17,14 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -jest.mock('../../../../api/branches', () => ({ deleteBranch: jest.fn() })); +jest.mock('../../../../../api/branches', () => ({ deleteBranch: jest.fn() })); import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import DeleteBranchModal from '../DeleteBranchModal'; -import { ShortLivingBranch, BranchType } from '../../../../app/types'; -import { submit, doAsync, click } from '../../../../helpers/testUtils'; -import { deleteBranch } from '../../../../api/branches'; +import { ShortLivingBranch, BranchType } from '../../../../../app/types'; +import { submit, doAsync, click } from '../../../../../helpers/testUtils'; +import { deleteBranch } from '../../../../../api/branches'; beforeEach(() => { (deleteBranch as jest.Mock<any>).mockClear(); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/NoBranchSupportPopup-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/NoBranchSupportPopup-test.tsx deleted file mode 100644 index 4f5925156fc..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/NoBranchSupportPopup-test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 NoBranchSupportPopup from '../NoBranchSupportPopup'; - -it('renders', () => { - expect(shallow(<NoBranchSupportPopup />)).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/RenameBranchModal-test.tsx index 3a1c962d68e..c1a0750e5bb 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/RenameBranchModal-test.tsx @@ -17,14 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -jest.mock('../../../../api/branches', () => ({ renameBranch: jest.fn() })); +jest.mock('../../../../../api/branches', () => ({ renameBranch: jest.fn() })); import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import RenameBranchModal from '../RenameBranchModal'; -import { MainBranch } from '../../../../app/types'; -import { submit, doAsync, click, change } from '../../../../helpers/testUtils'; -import { renameBranch } from '../../../../api/branches'; +import { MainBranch } from '../../../../../app/types'; +import { submit, doAsync, click, change } from '../../../../../helpers/testUtils'; +import { renameBranch } from '../../../../../api/branches'; beforeEach(() => { (renameBranch as jest.Mock<any>).mockClear(); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap index f84eeda97aa..a4317c09cec 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap @@ -50,7 +50,6 @@ exports[`renders 1`] = ` "qualifier": "TRK", } } - conf={Object {}} incremental={true} isFailed={true} isInProgress={true} @@ -72,7 +71,6 @@ exports[`renders 1`] = ` "qualifier": "TRK", } } - conf={Object {}} location={Object {}} /> </ContextNavBar> diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap index 38c20e78b97..1a1f5a3db79 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap @@ -26,47 +26,6 @@ exports[`renders main branch 1`] = ` </div> `; -exports[`renders no branch support popup 1`] = ` -<div - className="navbar-context-branches" -> - <BranchIcon - branch={ - Object { - "isMain": true, - "name": "master", - } - } - className="little-spacer-right" - color="#cdcdcd" - /> - <span - className="note" - > - master - </span> - <div - className="display-inline-block spacer-left" - > - <a - className="link-no-underline" - href="#" - onClick={[Function]} - > - <HelpIcon - fill="#cdcdcd" - /> - </a> - <BubblePopupHelper - isOpen={false} - popup={<NoBranchSupportPopup />} - position="bottomleft" - togglePopup={[Function]} - /> - </div> -</div> -`; - exports[`renders short-living branch 1`] = ` <div className="navbar-context-branches dropdown" diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap index 463fb48d493..eda0f3f1cb5 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap @@ -39,6 +39,7 @@ exports[`renders list 1`] = ` "key": "component", } } + onBranchesChange={[Function]} onSelect={[Function]} selected={true} /> @@ -78,6 +79,7 @@ exports[`renders list 1`] = ` "key": "component", } } + onBranchesChange={[Function]} onSelect={[Function]} selected={false} /> @@ -101,6 +103,7 @@ exports[`renders list 1`] = ` "key": "component", } } + onBranchesChange={[Function]} onSelect={[Function]} selected={false} /> @@ -120,6 +123,7 @@ exports[`renders list 1`] = ` "key": "component", } } + onBranchesChange={[Function]} onSelect={[Function]} selected={false} /> @@ -159,6 +163,7 @@ exports[`renders list 1`] = ` "key": "component", } } + onBranchesChange={[Function]} onSelect={[Function]} selected={false} /> @@ -213,6 +218,7 @@ exports[`searches 1`] = ` "key": "component", } } + onBranchesChange={[Function]} onSelect={[Function]} selected={true} /> @@ -232,6 +238,7 @@ exports[`searches 1`] = ` "key": "component", } } + onBranchesChange={[Function]} onSelect={[Function]} selected={false} /> diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap index f4edb48406f..ee665153336 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap @@ -17,7 +17,9 @@ exports[`renders main branch 1`] = ` } } > - <div> + <div + className="navbar-context-meta-branch-menu-item-name" + > <BranchIcon branch={ Object { @@ -70,7 +72,9 @@ exports[`renders short-living branch 1`] = ` } } > - <div> + <div + className="navbar-context-meta-branch-menu-item-name" + > <BranchIcon branch={ Object { @@ -132,7 +136,9 @@ exports[`renders short-living orhpan branch 1`] = ` } } > - <div> + <div + className="navbar-context-meta-branch-menu-item-name" + > <BranchIcon branch={ Object { diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap index ea4cd792997..7b305c2b763 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap @@ -136,23 +136,6 @@ exports[`should work for all qualifiers 1`] = ` style={Object {}} to={ Object { - "pathname": "/project/branches", - "query": Object { - "id": "foo", - }, - } - } - > - project_branches.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { "pathname": "/project/deletion", "query": Object { "id": "foo", @@ -755,8 +738,7 @@ exports[`should work for long-living branches 1`] = ` } } > - layout.settings - Â + branches.branch_settings </Link> </li> </NavBarTabs> @@ -1036,23 +1018,6 @@ exports[`should work with extensions 1`] = ` style={Object {}} to={ Object { - "pathname": "/project/branches", - "query": Object { - "id": "foo", - }, - } - } - > - project_branches.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { "pathname": "/project/admin/extension/foo", "query": Object { "id": "foo", @@ -1258,23 +1223,6 @@ exports[`should work with multiple extensions 1`] = ` style={Object {}} to={ Object { - "pathname": "/project/branches", - "query": Object { - "id": "foo", - }, - } - } - > - project_branches.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { "pathname": "/project/admin/extension/foo", "query": Object { "id": "foo", diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap index 934f8ed2d7d..934f8ed2d7d 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap deleted file mode 100644 index bcc5eadaede..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders 1`] = ` -<BubblePopup - customClass="bubble-popup-bottom" -> - <div - className="abs-width-400" - > - <h6 - className="spacer-bottom" - > - branches.no_support.header - </h6> - <p - className="big-spacer-bottom markdown" - > - branches.no_support.header.text - </p> - <p> - <a - href="https://redirect.sonarsource.com/doc/branches.html" - target="_blank" - > - learn_more - </a> - <a - className="button spacer-left" - href="https://www.sonarsource.com/company/contact/" - target="_blank" - > - buy_developer_pack - </a> - </p> - </div> -</BubblePopup> -`; diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap index 7867fa4785a..7867fa4785a 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 1db17ec0ffd..cc8717235f3 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -79,7 +79,7 @@ export interface Component { version?: string; } -export interface ComponentConfiguration { +interface ComponentConfiguration { extensions?: ComponentExtension[]; showBackgroundTasks?: boolean; showLinks?: boolean; diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js index b6577565efd..1b8f7f606c3 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -55,7 +55,6 @@ import permissionTemplatesRoutes from '../../apps/permission-templates/routes'; import portfolioRoutes from '../../apps/portfolio/routes'; import projectActivityRoutes from '../../apps/projectActivity/routes'; import projectAdminRoutes from '../../apps/project-admin/routes'; -import projectBranchesRoutes from '../../apps/projectBranches/routes'; import projectQualityGateRoutes from '../../apps/projectQualityGate/routes'; import projectQualityProfilesRoutes from '../../apps/projectQualityProfiles/routes'; import projectsRoutes from '../../apps/projects/routes'; @@ -207,7 +206,6 @@ const startReactApp = () => { component={ProjectAdminPageExtension} /> <Route path="project/background_tasks" childRoutes={backgroundTasksRoutes} /> - <Route path="project/branches" childRoutes={projectBranchesRoutes} /> <Route path="project/settings" childRoutes={settingsRoutes} /> <Route path="project_roles" childRoutes={projectPermissionsRoutes} /> </Route> diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.js b/server/sonar-web/src/main/js/apps/overview/components/App.js index 5c4b910f698..f7f3dbf4e86 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/App.js +++ b/server/sonar-web/src/main/js/apps/overview/components/App.js @@ -56,19 +56,23 @@ export default class App extends React.PureComponent { query: { id: this.props.component.key } }); } - if (isShortLivingBranch(this.props.branch)) { + if (isShortLivingBranch(this.props.branch) && !this.isFile()) { this.context.router.replace(getProjectBranchUrl(this.props.component.key, this.props.branch)); } } isPortfolio() { - return this.props.component.qualifier === 'VW' || this.props.component.qualifier === 'SVW'; + return ['VW', 'SVW'].includes(this.props.component.qualifier); + } + + isFile() { + return ['FIL', 'UTS'].includes(this.props.component.qualifier); } render() { const { branch, component } = this.props; - if (this.isPortfolio() || isShortLivingBranch(branch)) { + if (this.isPortfolio() || (isShortLivingBranch(branch) && !this.isFile())) { return null; } 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 deleted file mode 100644 index 02a8e20365f..00000000000 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 BranchRow from './BranchRow'; -import { Branch } from '../../../app/types'; -import { sortBranchesAsTree } from '../../../helpers/branches'; -import { translate } from '../../../helpers/l10n'; - -interface Props { - branches: Branch[]; - component: { key: string }; - onBranchesChange: () => void; -} - -export default function App({ branches, component, onBranchesChange }: Props) { - return ( - <div className="page page-limited"> - <header className="page-header"> - <h1 className="page-title">{translate('project_branches.page')}</h1> - </header> - - <table className="data zebra zebra-hover"> - <thead> - <tr> - <th>{translate('branch')}</th> - <th className="text-right">{translate('status')}</th> - <th className="text-right">{translate('actions')}</th> - </tr> - </thead> - <tbody> - {sortBranchesAsTree(branches).map(branch => ( - <BranchRow - branch={branch} - component={component.key} - key={branch.name} - onChange={onBranchesChange} - /> - ))} - </tbody> - </table> - </div> - ); -} 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 deleted file mode 100644 index e163481d759..00000000000 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 { Branch } from '../../../app/types'; -import * as classNames from 'classnames'; -import DeleteBranchModal from './DeleteBranchModal'; -import BranchStatus from '../../../components/common/BranchStatus'; -import BranchIcon from '../../../components/icons-components/BranchIcon'; -import { isShortLivingBranch } from '../../../helpers/branches'; -import ChangeIcon from '../../../components/icons-components/ChangeIcon'; -import DeleteIcon from '../../../components/icons-components/DeleteIcon'; -import { translate } from '../../../helpers/l10n'; -import Tooltip from '../../../components/controls/Tooltip'; -import RenameBranchModal from './RenameBranchModal'; - -interface Props { - branch: Branch; - component: string; - onChange: () => void; -} - -interface State { - deleting: boolean; - renaming: boolean; -} - -export default class BranchRow extends React.PureComponent<Props, State> { - mounted: boolean; - state: State = { deleting: false, renaming: false }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - handleDeleteClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.setState({ deleting: true }); - }; - - handleDeletingStop = () => { - this.setState({ deleting: false }); - }; - - handleRenameClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.setState({ renaming: true }); - }; - - handleChange = () => { - if (this.mounted) { - this.setState({ deleting: false, renaming: false }); - this.props.onChange(); - } - }; - - handleRenamingStop = () => { - this.setState({ renaming: false }); - }; - - render() { - const { branch, component } = this.props; - - return ( - <tr> - <td> - <BranchIcon - branch={branch} - className={classNames('little-spacer-right', { - 'big-spacer-left': isShortLivingBranch(branch) && !branch.isOrphan - })} - /> - {branch.name} - {branch.isMain && ( - <div className="outline-badge spacer-left">{translate('branches.main_branch')}</div> - )} - </td> - <td className="thin nowrap text-right"> - <BranchStatus branch={branch} /> - </td> - <td className="thin nowrap text-right"> - {branch.isMain ? ( - <Tooltip overlay={translate('branches.rename')}> - <a className="js-rename link-no-underline" href="#" onClick={this.handleRenameClick}> - <ChangeIcon /> - </a> - </Tooltip> - ) : ( - <Tooltip overlay={translate('branches.delete')}> - <a className="js-delete link-no-underline" href="#" onClick={this.handleDeleteClick}> - <DeleteIcon /> - </a> - </Tooltip> - )} - </td> - - {this.state.deleting && ( - <DeleteBranchModal - branch={branch} - component={component} - onClose={this.handleDeletingStop} - onDelete={this.handleChange} - /> - )} - - {this.state.renaming && ( - <RenameBranchModal - branch={branch} - component={component} - onClose={this.handleRenamingStop} - onRename={this.handleChange} - /> - )} - </tr> - ); - } -} 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 deleted file mode 100644 index 4288105f79a..00000000000 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 App from '../App'; -import { Branch, BranchType } from '../../../../app/types'; - -it('renders sorted list of branches', () => { - const branches: Branch[] = [ - { isMain: true, name: 'master' }, - { isMain: false, name: 'branch-1.0', type: BranchType.LONG }, - { isMain: false, name: 'branch-1.0', mergeBranch: 'master', type: BranchType.SHORT } - ]; - expect( - shallow(<App branches={branches} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />) - ).toMatchSnapshot(); -}); 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 deleted file mode 100644 index 4edc3ce70d6..00000000000 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 BranchRow from '../BranchRow'; -import { MainBranch, ShortLivingBranch, BranchType } from '../../../../app/types'; -import { click } from '../../../../helpers/testUtils'; - -const mainBranch: MainBranch = { isMain: true, name: 'master' }; - -const shortBranch: ShortLivingBranch = { - isMain: false, - name: 'feature', - mergeBranch: 'foo', - type: BranchType.SHORT -}; - -it('renders main branch', () => { - expect(shallowRender(mainBranch)).toMatchSnapshot(); -}); - -it('renders short-living branch', () => { - expect(shallowRender(shortBranch)).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(); -}); - -function shallowRender(branch: MainBranch | ShortLivingBranch, onChange: () => void = jest.fn()) { - const wrapper = shallow(<BranchRow branch={branch} component="foo" onChange={onChange} />); - (wrapper.instance() as any).mounted = true; - return wrapper; -} 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 deleted file mode 100644 index 6f983e33df8..00000000000 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap +++ /dev/null @@ -1,73 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders sorted list of branches 1`] = ` -<div - className="page page-limited" -> - <header - className="page-header" - > - <h1 - className="page-title" - > - project_branches.page - </h1> - </header> - <table - className="data zebra zebra-hover" - > - <thead> - <tr> - <th> - branch - </th> - <th - className="text-right" - > - status - </th> - <th - className="text-right" - > - actions - </th> - </tr> - </thead> - <tbody> - <BranchRow - branch={ - Object { - "isMain": true, - "name": "master", - } - } - component="foo" - onChange={[Function]} - /> - <BranchRow - branch={ - Object { - "isMain": false, - "mergeBranch": "master", - "name": "branch-1.0", - "type": "SHORT", - } - } - component="foo" - onChange={[Function]} - /> - <BranchRow - branch={ - Object { - "isMain": false, - "name": "branch-1.0", - "type": "LONG", - } - } - component="foo" - onChange={[Function]} - /> - </tbody> - </table> -</div> -`; 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 deleted file mode 100644 index ea135765ece..00000000000 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap +++ /dev/null @@ -1,100 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders main branch 1`] = ` -<tr> - <td> - <BranchIcon - branch={ - Object { - "isMain": true, - "name": "master", - } - } - className="little-spacer-right" - /> - master - <div - className="outline-badge spacer-left" - > - branches.main_branch - </div> - </td> - <td - className="thin nowrap text-right" - > - <BranchStatus - branch={ - Object { - "isMain": true, - "name": "master", - } - } - /> - </td> - <td - className="thin nowrap text-right" - > - <Tooltip - overlay="branches.rename" - placement="bottom" - > - <a - className="js-rename link-no-underline" - href="#" - onClick={[Function]} - > - <ChangeIcon /> - </a> - </Tooltip> - </td> -</tr> -`; - -exports[`renders short-living branch 1`] = ` -<tr> - <td> - <BranchIcon - branch={ - Object { - "isMain": false, - "mergeBranch": "foo", - "name": "feature", - "type": "SHORT", - } - } - className="little-spacer-right big-spacer-left" - /> - feature - </td> - <td - className="thin nowrap text-right" - > - <BranchStatus - branch={ - Object { - "isMain": false, - "mergeBranch": "foo", - "name": "feature", - "type": "SHORT", - } - } - /> - </td> - <td - className="thin nowrap text-right" - > - <Tooltip - overlay="branches.delete" - placement="bottom" - > - <a - className="js-delete link-no-underline" - href="#" - onClick={[Function]} - > - <DeleteIcon /> - </a> - </Tooltip> - </td> -</tr> -`; diff --git a/server/sonar-web/src/main/js/apps/projectBranches/routes.ts b/server/sonar-web/src/main/js/apps/projectBranches/routes.ts deleted file mode 100644 index 520805ebac5..00000000000 --- a/server/sonar-web/src/main/js/apps/projectBranches/routes.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 { RouterState, IndexRouteProps } from 'react-router'; - -const routes = [ - { - getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { - import('./components/App').then(i => callback(null, { component: (i as any).default })); - } - } -]; - -export default routes; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx index 9c4d70c270e..c32b99adfcb 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx @@ -34,6 +34,7 @@ interface Props { } interface State { + advanced: boolean; branch: string; createdProject?: { key: string; name: string }; key: string; @@ -50,6 +51,7 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> constructor(props: Props) { super(props); this.state = { + advanced: false, branch: '', key: '', loading: false, @@ -71,6 +73,12 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> this.props.onClose(); }; + handleAdvancedClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { + event.preventDefault(); + event.currentTarget.blur(); + this.setState(state => ({ advanced: !state.advanced })); + }; + handleInputChange = (event: React.SyntheticEvent<HTMLInputElement>) => { const { name, value } = event.currentTarget; this.setState({ [name]: value }); @@ -163,17 +171,6 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> /> </div> <div className="modal-field"> - <label htmlFor="create-project-branch">{translate('branch')}</label> - <input - id="create-project-branch" - maxLength={200} - name="branch" - onChange={this.handleInputChange} - type="text" - value={this.state.branch} - /> - </div> - <div className="modal-field"> <label htmlFor="create-project-key"> {translate('key')} <em className="mandatory">*</em> @@ -202,6 +199,29 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> </div> )} </div> + {this.state.advanced ? ( + <div className="modal-field big-spacer-top"> + <label htmlFor="create-project-branch">{translate('branch')}</label> + <input + autoFocus={true} + id="create-project-branch" + maxLength={200} + name="branch" + onChange={this.handleInputChange} + type="text" + value={this.state.branch} + /> + </div> + ) : ( + <div className="modal-field big-spacer-top"> + <a + className="js-more text-muted note" + href="#" + onClick={this.handleAdvancedClick}> + {translate('more')} + </a> + </div> + )} </div> <footer className="modal-foot"> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx index 0212d094e8e..bd8c3114b4e 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx @@ -26,7 +26,7 @@ jest.mock('../../../api/components', () => ({ import * as React from 'react'; import { shallow } from 'enzyme'; import CreateProjectForm from '../CreateProjectForm'; -import { change, submit } from '../../../helpers/testUtils'; +import { change, submit, click } from '../../../helpers/testUtils'; const createProject = require('../../../api/components').createProject as jest.Mock<any>; @@ -46,9 +46,6 @@ it('creates project', async () => { change(wrapper.find('input[name="name"]'), 'name', { currentTarget: { name: 'name', value: 'name' } }); - change(wrapper.find('input[name="branch"]'), 'branch', { - currentTarget: { name: 'branch', value: 'branch' } - }); change(wrapper.find('input[name="key"]'), 'key', { currentTarget: { name: 'key', value: 'key' } }); @@ -58,7 +55,7 @@ it('creates project', async () => { submit(wrapper.find('form')); expect(createProject).toBeCalledWith({ - branch: 'branch', + branch: '', name: 'name', organization: 'org', project: 'key', @@ -66,7 +63,21 @@ it('creates project', async () => { }); expect(wrapper).toMatchSnapshot(); - await new Promise(resolve => setImmediate(resolve)); + await new Promise(setImmediate); wrapper.update(); expect(wrapper).toMatchSnapshot(); }); + +it('shows more', () => { + const wrapper = shallow( + <CreateProjectForm + onClose={jest.fn()} + onProjectCreated={jest.fn()} + organization={organization} + /> + ); + expect(wrapper).toMatchSnapshot(); + + click(wrapper.find('.js-more')); + expect(wrapper).toMatchSnapshot(); +}); 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 036b78af957..332863ffafe 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 @@ -56,23 +56,6 @@ exports[`creates project 1`] = ` className="modal-field" > <label - htmlFor="create-project-branch" - > - branch - </label> - <input - id="create-project-branch" - maxLength={200} - name="branch" - onChange={[Function]} - type="text" - value="" - /> - </div> - <div - className="modal-field" - > - <label htmlFor="create-project-key" > key @@ -111,6 +94,17 @@ exports[`creates project 1`] = ` /> </div> </div> + <div + className="modal-field big-spacer-top" + > + <a + className="js-more text-muted note" + href="#" + onClick={[Function]} + > + more + </a> + </div> </div> <footer className="modal-foot" @@ -190,23 +184,6 @@ exports[`creates project 2`] = ` className="modal-field" > <label - htmlFor="create-project-branch" - > - branch - </label> - <input - id="create-project-branch" - maxLength={200} - name="branch" - onChange={[Function]} - type="text" - value="branch" - /> - </div> - <div - className="modal-field" - > - <label htmlFor="create-project-key" > key @@ -245,6 +222,17 @@ exports[`creates project 2`] = ` /> </div> </div> + <div + className="modal-field big-spacer-top" + > + <a + className="js-more text-muted note" + href="#" + onClick={[Function]} + > + more + </a> + </div> </div> <footer className="modal-foot" @@ -324,23 +312,6 @@ exports[`creates project 3`] = ` className="modal-field" > <label - htmlFor="create-project-branch" - > - branch - </label> - <input - id="create-project-branch" - maxLength={200} - name="branch" - onChange={[Function]} - type="text" - value="branch" - /> - </div> - <div - className="modal-field" - > - <label htmlFor="create-project-key" > key @@ -379,6 +350,17 @@ exports[`creates project 3`] = ` /> </div> </div> + <div + className="modal-field big-spacer-top" + > + <a + className="js-more text-muted note" + href="#" + onClick={[Function]} + > + more + </a> + </div> </div> <footer className="modal-foot" @@ -467,3 +449,266 @@ exports[`creates project 4`] = ` </div> </Modal> `; + +exports[`shows more 1`] = ` +<Modal + ariaHideApp={true} + bodyOpenClassName="ReactModal__Body--open" + className="modal" + closeTimeoutMS={0} + contentLabel="modal form" + isOpen={true} + onRequestClose={[Function]} + overlayClassName="modal-overlay" + parentSelector={[Function]} + portalClassName="ReactModalPortal" + shouldCloseOnOverlayClick={true} +> + <form + id="create-project-form" + onSubmit={[Function]} + > + <header + className="modal-head" + > + <h2> + qualifiers.create.TRK + </h2> + </header> + <div + className="modal-body" + > + <div + className="modal-field" + > + <label + htmlFor="create-project-name" + > + name + <em + className="mandatory" + > + * + </em> + </label> + <input + autoFocus={true} + id="create-project-name" + maxLength={2000} + name="name" + onChange={[Function]} + required={true} + type="text" + value="" + /> + </div> + <div + className="modal-field" + > + <label + htmlFor="create-project-key" + > + key + <em + className="mandatory" + > + * + </em> + </label> + <input + id="create-project-key" + maxLength={400} + name="key" + onChange={[Function]} + required={true} + type="text" + value="" + /> + </div> + <div + className="modal-field" + > + <label> + visibility + </label> + <VisibilitySelector + className="little-spacer-top" + onChange={[Function]} + visibility="public" + /> + <div + className="spacer-top" + > + <UpgradeOrganizationBox + organization="org" + /> + </div> + </div> + <div + className="modal-field big-spacer-top" + > + <a + className="js-more text-muted note" + href="#" + onClick={[Function]} + > + more + </a> + </div> + </div> + <footer + className="modal-foot" + > + <button + disabled={false} + id="create-project-submit" + type="submit" + > + create + </button> + <a + href="#" + id="create-project-cancel" + onClick={[Function]} + > + cancel + </a> + </footer> + </form> +</Modal> +`; + +exports[`shows more 2`] = ` +<Modal + ariaHideApp={true} + bodyOpenClassName="ReactModal__Body--open" + className="modal" + closeTimeoutMS={0} + contentLabel="modal form" + isOpen={true} + onRequestClose={[Function]} + overlayClassName="modal-overlay" + parentSelector={[Function]} + portalClassName="ReactModalPortal" + shouldCloseOnOverlayClick={true} +> + <form + id="create-project-form" + onSubmit={[Function]} + > + <header + className="modal-head" + > + <h2> + qualifiers.create.TRK + </h2> + </header> + <div + className="modal-body" + > + <div + className="modal-field" + > + <label + htmlFor="create-project-name" + > + name + <em + className="mandatory" + > + * + </em> + </label> + <input + autoFocus={true} + id="create-project-name" + maxLength={2000} + name="name" + onChange={[Function]} + required={true} + type="text" + value="" + /> + </div> + <div + className="modal-field" + > + <label + htmlFor="create-project-key" + > + key + <em + className="mandatory" + > + * + </em> + </label> + <input + id="create-project-key" + maxLength={400} + name="key" + onChange={[Function]} + required={true} + type="text" + value="" + /> + </div> + <div + className="modal-field" + > + <label> + visibility + </label> + <VisibilitySelector + className="little-spacer-top" + onChange={[Function]} + visibility="public" + /> + <div + className="spacer-top" + > + <UpgradeOrganizationBox + organization="org" + /> + </div> + </div> + <div + className="modal-field big-spacer-top" + > + <label + htmlFor="create-project-branch" + > + branch + </label> + <input + autoFocus={true} + id="create-project-branch" + maxLength={200} + name="branch" + onChange={[Function]} + type="text" + value="" + /> + </div> + </div> + <footer + className="modal-foot" + > + <button + disabled={false} + id="create-project-submit" + type="submit" + > + create + </button> + <a + href="#" + id="create-project-cancel" + onClick={[Function]} + > + cancel + </a> + </footer> + </form> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/App.js b/server/sonar-web/src/main/js/apps/settings/components/App.js index 866830fbb69..dff1d0b74d5 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/App.js +++ b/server/sonar-web/src/main/js/apps/settings/components/App.js @@ -98,7 +98,7 @@ export default class App extends React.PureComponent { link: ( <Link to={{ - pathname: '/project/branches', + pathname: '/project/settings', query: { id: this.props.component && this.props.component.key } }}> {translate('branches.settings_hint_tab')} diff --git a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx b/server/sonar-web/src/main/js/components/common/BranchStatus.tsx index ea1604dfa5c..40688087cf0 100644 --- a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx +++ b/server/sonar-web/src/main/js/components/common/BranchStatus.tsx @@ -41,35 +41,35 @@ export default function BranchStatus({ branch, concise = false }: Props) { const totalIssues = branch.status.bugs + branch.status.vulnerabilities + branch.status.codeSmells; - return ( + const indicatorClassName = classNames('branch-status-indicator', { + 'is-failed': totalIssues > 0, + 'is-passed': totalIssues === 0 + }); + + return concise ? ( <ul className="list-inline branch-status"> + <li>{totalIssues}</li> + <li className="spacer-left"> + <i className={indicatorClassName} /> + </li> + </ul> + ) : ( + <ul className="list-inline branch-status"> + <li className="spacer-right"> + <i className={indicatorClassName} /> + </li> + <li> + {branch.status.bugs} + <BugIcon /> + </li> + <li> + {branch.status.vulnerabilities} + <VulnerabilityIcon /> + </li> <li> - <i - className={classNames('branch-status-indicator', { - 'is-failed': totalIssues > 0, - 'is-passed': totalIssues === 0 - })} - /> + {branch.status.codeSmells} + <CodeSmellIcon /> </li> - {concise && <li>{totalIssues}</li>} - {!concise && ( - <li> - {branch.status.bugs} - <BugIcon className="little-spacer-left" /> - </li> - )} - {!concise && ( - <li> - {branch.status.vulnerabilities} - <VulnerabilityIcon className="little-spacer-left" /> - </li> - )} - {!concise && ( - <li> - {branch.status.codeSmells} - <CodeSmellIcon className="little-spacer-left" /> - </li> - )} </ul> ); } else { diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap index 1f4ccfc4484..b106929fbe4 100644 --- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap @@ -20,28 +20,24 @@ exports[`renders status of short-living branches 1`] = ` <ul className="list-inline branch-status" > - <li> + <li + className="spacer-right" + > <i className="branch-status-indicator is-passed" /> </li> <li> 0 - <BugIcon - className="little-spacer-left" - /> + <BugIcon /> </li> <li> 0 - <VulnerabilityIcon - className="little-spacer-left" - /> + <VulnerabilityIcon /> </li> <li> 0 - <CodeSmellIcon - className="little-spacer-left" - /> + <CodeSmellIcon /> </li> </ul> `; @@ -50,28 +46,24 @@ exports[`renders status of short-living branches 2`] = ` <ul className="list-inline branch-status" > - <li> + <li + className="spacer-right" + > <i className="branch-status-indicator is-failed" /> </li> <li> 0 - <BugIcon - className="little-spacer-left" - /> + <BugIcon /> </li> <li> 0 - <VulnerabilityIcon - className="little-spacer-left" - /> + <VulnerabilityIcon /> </li> <li> 1 - <CodeSmellIcon - className="little-spacer-left" - /> + <CodeSmellIcon /> </li> </ul> `; @@ -80,28 +72,24 @@ exports[`renders status of short-living branches 3`] = ` <ul className="list-inline branch-status" > - <li> + <li + className="spacer-right" + > <i className="branch-status-indicator is-failed" /> </li> <li> 7 - <BugIcon - className="little-spacer-left" - /> + <BugIcon /> </li> <li> 6 - <VulnerabilityIcon - className="little-spacer-left" - /> + <VulnerabilityIcon /> </li> <li> 3 - <CodeSmellIcon - className="little-spacer-left" - /> + <CodeSmellIcon /> </li> </ul> `; |