]> source.dussan.org Git - sonarqube.git/commitdiff
apply branches feedback (#2535)
authorStas Vilchik <stas.vilchik@sonarsource.com>
Fri, 22 Sep 2017 11:56:45 +0000 (13:56 +0200)
committerGitHub <noreply@github.com>
Fri, 22 Sep 2017 11:56:45 +0000 (13:56 +0200)
52 files changed:
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
server/sonar-web/src/main/js/app/components/nav/component/DeleteBranchModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/RenameBranchModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/DeleteBranchModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/NoBranchSupportPopup-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/RenameBranchModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/app/utils/startReactApp.js
server/sonar-web/src/main/js/apps/overview/components/App.js
server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectBranches/routes.ts [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/App.js
server/sonar-web/src/main/js/components/common/BranchStatus.tsx
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 085b4be1979b03e4e108e848d0dab7c2f3dcea5e..a1d01845343e8ad134ba91a32a6c11737eb5675e 100644 (file)
@@ -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>
     );
   }
index 105931959f9a3b47ee5f2f1edb1d1d2c8f51e3ca..01ff6e3a4fab67be74c4a7abc3c01a84d92b9ea6 100644 (file)
@@ -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');
-});
index 5f3958f35bde7d43493b2eaff9ee2c5464f30a58..347ea1a3df702c5ad86b2d5fb58f10828671b74f 100644 (file)
 .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;
 }
index 1c7a748ecef8758b1d64755d40a726e1c2231e85..5d78833af2de7b85884a50da67bfffecc1e0e740 100644 (file)
@@ -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}
         />
index dd5ecaa3020d048d7afacda7098d17e3190e3d67..21010a1ec690d32028da273c54130bdc17f462b5 100644 (file)
@@ -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) {
index 9f41411bbad610aff31a4d61040df8f1fecde7b0..f4ea43b0bc68d771115c6d7b13e2530f89bcaca3 100644 (file)
@@ -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>
     );
   }
index fa49b53d2d8ad50452fdb5be4a5617d75b70268b..a1e02a1aa8f9d72327c6ed926ef12cad8312da48 100644 (file)
 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>
+    );
+  }
 }
index 625043eb9234b014524ca11aa289d52be25e8182..270829acee0ada8feaecff580c018e7af2f1ad29 100644 (file)
@@ -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')}&nbsp;
+            {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')}&nbsp;
           <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>
     );
   }
index 4c1e485000cdb76e636999b0b66721f70129a24e..c5721aa4e24f997cd0c92cbc7735b0a87e57fb51 100644 (file)
@@ -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/app/components/nav/component/DeleteBranchModal.tsx b/server/sonar-web/src/main/js/app/components/nav/component/DeleteBranchModal.tsx
new file mode 100644 (file)
index 0000000..2273692
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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 Modal from 'react-modal';
+import { deleteBranch } from '../../../../api/branches';
+import { Branch } from '../../../../app/types';
+import { translate, translateWithParameters } from '../../../../helpers/l10n';
+
+interface Props {
+  branch: Branch;
+  component: string;
+  onClose: () => void;
+  onDelete: () => void;
+}
+
+interface State {
+  loading: boolean;
+}
+
+export default class DeleteBranchModal extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = { loading: false };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+    event.preventDefault();
+    this.setState({ loading: true });
+    deleteBranch(this.props.component, this.props.branch.name).then(
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+          this.props.onDelete();
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
+  };
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    event.stopPropagation();
+    this.props.onClose();
+  };
+
+  render() {
+    const { branch } = this.props;
+    const header = translate('branches.delete');
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <header className="modal-head">
+          <h2>{header}</h2>
+        </header>
+        <form onSubmit={this.handleSubmit}>
+          <div className="modal-body">
+            {translateWithParameters('branches.delete.are_you_sure', branch.name)}
+          </div>
+          <footer className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button className="button-red" disabled={this.state.loading} type="submit">
+              {translate('delete')}
+            </button>
+            <a href="#" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </footer>
+        </form>
+      </Modal>
+    );
+  }
+}
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 (file)
index db44804..0000000
+++ /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/app/components/nav/component/RenameBranchModal.tsx b/server/sonar-web/src/main/js/app/components/nav/component/RenameBranchModal.tsx
new file mode 100644 (file)
index 0000000..b17401c
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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 Modal from 'react-modal';
+import { renameBranch } from '../../../../api/branches';
+import { Branch } from '../../../../app/types';
+import { translate } from '../../../../helpers/l10n';
+
+interface Props {
+  branch: Branch;
+  component: string;
+  onClose: () => void;
+  onRename: () => void;
+}
+
+interface State {
+  loading: boolean;
+  name?: string;
+}
+
+export default class RenameBranchModal extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = { loading: false };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+    event.preventDefault();
+    if (!this.state.name) {
+      return;
+    }
+    this.setState({ loading: true });
+    renameBranch(this.props.component, this.state.name).then(
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+          this.props.onRename();
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
+  };
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    event.stopPropagation();
+    this.props.onClose();
+  };
+
+  handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+    this.setState({ name: event.currentTarget.value });
+  };
+
+  render() {
+    const { branch } = this.props;
+    const header = translate('branches.rename');
+    const submitDisabled =
+      this.state.loading || !this.state.name || this.state.name === branch.name;
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <header className="modal-head">
+          <h2>{header}</h2>
+        </header>
+        <form onSubmit={this.handleSubmit}>
+          <div className="modal-body">
+            <div className="modal-field">
+              <label htmlFor="rename-branch-name">
+                {translate('new_name')}
+                <em className="mandatory">*</em>
+              </label>
+              <input
+                autoFocus={true}
+                id="rename-branch-name"
+                maxLength={100}
+                name="name"
+                onChange={this.handleNameChange}
+                required={true}
+                size={50}
+                type="text"
+                value={this.state.name != undefined ? this.state.name : branch.name}
+              />
+            </div>
+          </div>
+          <footer className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button disabled={submitDisabled} type="submit">
+              {translate('rename')}
+            </button>
+            <a href="#" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </footer>
+        </form>
+      </Modal>
+    );
+  }
+}
index 6ae5fb1c7685faf96125561454e653d84171710d..6adc8751910701215f4fe201ed2347f3d5d2c35c 100644 (file)
@@ -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,
index 28931e0fc515a75d8070aa307c9618ac58928f09..5a2343bb4c5da8818c5b45c70887ff968f2c33eb 100644 (file)
@@ -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();
 });
index a4eb5bfbf82837063b2d38159c696d6a130c28cc..13e251e059afd1e1d4fe47aa31006d7a8bd0f9bc 100644 (file)
@@ -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);
index 2e1d56e21150ca09b3eebf02f08178416a86c2d7..c8ff060fa562159d7e02a36d26d2bd616ab2b6c4 100644 (file)
  */
 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}
+    />
+  );
+}
index 2e4af3fb2eee2131c3ff8731b13b67bfe91bf78b..8405e371da7bdb2024814d81a31b1110e1ae5d4f 100644 (file)
 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();
   }
 });
index 6949044c9ca8084b459fb4fee1bf7f494445bef7..1e1d412852c0633c1d773d31eb4390f4f11c1535 100644 (file)
@@ -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/app/components/nav/component/__tests__/DeleteBranchModal-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/DeleteBranchModal-test.tsx
new file mode 100644 (file)
index 0000000..0df5a12
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+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';
+
+beforeEach(() => {
+  (deleteBranch as jest.Mock<any>).mockClear();
+});
+
+it('renders', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot();
+  wrapper.setState({ loading: true });
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('deletes branch', () => {
+  (deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.resolve());
+  const onDelete = jest.fn();
+  const wrapper = shallowRender(onDelete);
+
+  submitForm(wrapper);
+
+  return doAsync().then(() => {
+    wrapper.update();
+    expect(wrapper.state().loading).toBe(false);
+    expect(onDelete).toBeCalled();
+    expect(deleteBranch).toBeCalledWith('foo', 'feature');
+  });
+});
+
+it('cancels', () => {
+  const onClose = jest.fn();
+  const wrapper = shallowRender(jest.fn(), onClose);
+
+  click(wrapper.find('a'));
+
+  return doAsync().then(() => {
+    expect(onClose).toBeCalled();
+  });
+});
+
+it('stops loading on WS error', () => {
+  (deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.reject(null));
+  const onDelete = jest.fn();
+  const wrapper = shallowRender(onDelete);
+
+  submitForm(wrapper);
+
+  return doAsync().then(() => {
+    wrapper.update();
+    expect(wrapper.state().loading).toBe(false);
+    expect(onDelete).not.toBeCalled();
+    expect(deleteBranch).toBeCalledWith('foo', 'feature');
+  });
+});
+
+function shallowRender(onDelete: () => void = jest.fn(), onClose: () => void = jest.fn()) {
+  const branch: ShortLivingBranch = {
+    isMain: false,
+    name: 'feature',
+    mergeBranch: 'master',
+    type: BranchType.SHORT
+  };
+  const wrapper = shallow(
+    <DeleteBranchModal branch={branch} component="foo" onClose={onClose} onDelete={onDelete} />
+  );
+  (wrapper.instance() as any).mounted = true;
+  return wrapper;
+}
+
+function submitForm(wrapper: ShallowWrapper<any, any>) {
+  submit(wrapper.find('form'));
+  expect(wrapper.state().loading).toBe(true);
+}
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 (file)
index 4f59251..0000000
+++ /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/app/components/nav/component/__tests__/RenameBranchModal-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/RenameBranchModal-test.tsx
new file mode 100644 (file)
index 0000000..c1a0750
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+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';
+
+beforeEach(() => {
+  (renameBranch as jest.Mock<any>).mockClear();
+});
+
+it('renders', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot();
+  wrapper.setState({ name: 'dev' });
+  expect(wrapper).toMatchSnapshot();
+  wrapper.setState({ loading: true });
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('renames branch', () => {
+  (renameBranch as jest.Mock<any>).mockImplementation(() => Promise.resolve());
+  const onRename = jest.fn();
+  const wrapper = shallowRender(onRename);
+
+  fillAndSubmit(wrapper);
+
+  return doAsync().then(() => {
+    wrapper.update();
+    expect(wrapper.state().loading).toBe(false);
+    expect(onRename).toBeCalled();
+    expect(renameBranch).toBeCalledWith('foo', 'dev');
+  });
+});
+
+it('cancels', () => {
+  const onClose = jest.fn();
+  const wrapper = shallowRender(jest.fn(), onClose);
+
+  click(wrapper.find('a'));
+
+  return doAsync().then(() => {
+    expect(onClose).toBeCalled();
+  });
+});
+
+it('stops loading on WS error', () => {
+  (renameBranch as jest.Mock<any>).mockImplementation(() => Promise.reject(null));
+  const onRename = jest.fn();
+  const wrapper = shallowRender(onRename);
+
+  fillAndSubmit(wrapper);
+
+  return doAsync().then(() => {
+    wrapper.update();
+    expect(wrapper.state().loading).toBe(false);
+    expect(onRename).not.toBeCalled();
+  });
+});
+
+function shallowRender(onRename: () => void = jest.fn(), onClose: () => void = jest.fn()) {
+  const branch: MainBranch = { isMain: true, name: 'master' };
+  const wrapper = shallow(
+    <RenameBranchModal branch={branch} component="foo" onClose={onClose} onRename={onRename} />
+  );
+  (wrapper.instance() as any).mounted = true;
+  return wrapper;
+}
+
+function fillAndSubmit(wrapper: ShallowWrapper<any, any>) {
+  change(wrapper.find('input'), 'dev');
+  submit(wrapper.find('form'));
+  expect(wrapper.state().loading).toBe(true);
+}
index f84eeda97aa208d453cc220ebed12fdfdb4199db..a4317c09cec400e150aedd9a8d728d898d2ff161 100644 (file)
@@ -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>
index 38c20e78b975288baf3ad1eba8763eb5c3113bf5..1a1f5a3db793fb9fe6b280cb81eb9b982644dde4 100644 (file)
@@ -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"
index 463fb48d4936106b6e20ce780730c569323ca20b..eda0f3f1cb5d18f9d84246d3400464d091503c0c 100644 (file)
@@ -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}
     />
index f4edb48406f4a7accec3edbc552fa09875c58774..ee6651533363acee8492d266791095b121f2ce3a 100644 (file)
@@ -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 {
index ea4cd792997901a94969c5059694fe4c79bfd656..7b305c2b7636bfc4c1806da07042f3e949092f0d 100644 (file)
@@ -129,23 +129,6 @@ exports[`should work for all qualifiers 1`] = `
           project_settings.page
         </Link>
       </li>
-      <li>
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/branches",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          project_branches.page
-        </Link>
-      </li>
       <li>
         <Link
           activeClassName="active"
@@ -755,8 +738,7 @@ exports[`should work for long-living branches 1`] = `
         }
       }
     >
-      layout.settings
-      Â 
+      branches.branch_settings
     </Link>
   </li>
 </NavBarTabs>
@@ -1029,23 +1011,6 @@ exports[`should work with extensions 1`] = `
           project_settings.page
         </Link>
       </li>
-      <li>
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/branches",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          project_branches.page
-        </Link>
-      </li>
       <li>
         <Link
           activeClassName="active"
@@ -1251,23 +1216,6 @@ exports[`should work with multiple extensions 1`] = `
           project_settings.page
         </Link>
       </li>
-      <li>
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/branches",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          project_branches.page
-        </Link>
-      </li>
       <li>
         <Link
           activeClassName="active"
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..934f8ed
--- /dev/null
@@ -0,0 +1,104 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<Modal
+  ariaHideApp={true}
+  bodyOpenClassName="ReactModal__Body--open"
+  className="modal"
+  closeTimeoutMS={0}
+  contentLabel="branches.delete"
+  isOpen={true}
+  onRequestClose={[Function]}
+  overlayClassName="modal-overlay"
+  parentSelector={[Function]}
+  portalClassName="ReactModalPortal"
+  shouldCloseOnOverlayClick={true}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      branches.delete
+    </h2>
+  </header>
+  <form
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-body"
+    >
+      branches.delete.are_you_sure.feature
+    </div>
+    <footer
+      className="modal-foot"
+    >
+      <button
+        className="button-red"
+        disabled={false}
+        type="submit"
+      >
+        delete
+      </button>
+      <a
+        href="#"
+        onClick={[Function]}
+      >
+        cancel
+      </a>
+    </footer>
+  </form>
+</Modal>
+`;
+
+exports[`renders 2`] = `
+<Modal
+  ariaHideApp={true}
+  bodyOpenClassName="ReactModal__Body--open"
+  className="modal"
+  closeTimeoutMS={0}
+  contentLabel="branches.delete"
+  isOpen={true}
+  onRequestClose={[Function]}
+  overlayClassName="modal-overlay"
+  parentSelector={[Function]}
+  portalClassName="ReactModalPortal"
+  shouldCloseOnOverlayClick={true}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      branches.delete
+    </h2>
+  </header>
+  <form
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-body"
+    >
+      branches.delete.are_you_sure.feature
+    </div>
+    <footer
+      className="modal-foot"
+    >
+      <i
+        className="spinner spacer-right"
+      />
+      <button
+        className="button-red"
+        disabled={true}
+        type="submit"
+      >
+        delete
+      </button>
+      <a
+        href="#"
+        onClick={[Function]}
+      >
+        cancel
+      </a>
+    </footer>
+  </form>
+</Modal>
+`;
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 (file)
index bcc5ead..0000000
+++ /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/app/components/nav/component/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..7867fa4
--- /dev/null
@@ -0,0 +1,223 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<Modal
+  ariaHideApp={true}
+  bodyOpenClassName="ReactModal__Body--open"
+  className="modal"
+  closeTimeoutMS={0}
+  contentLabel="branches.rename"
+  isOpen={true}
+  onRequestClose={[Function]}
+  overlayClassName="modal-overlay"
+  parentSelector={[Function]}
+  portalClassName="ReactModalPortal"
+  shouldCloseOnOverlayClick={true}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      branches.rename
+    </h2>
+  </header>
+  <form
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-body"
+    >
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="rename-branch-name"
+        >
+          new_name
+          <em
+            className="mandatory"
+          >
+            *
+          </em>
+        </label>
+        <input
+          autoFocus={true}
+          id="rename-branch-name"
+          maxLength={100}
+          name="name"
+          onChange={[Function]}
+          required={true}
+          size={50}
+          type="text"
+          value="master"
+        />
+      </div>
+    </div>
+    <footer
+      className="modal-foot"
+    >
+      <button
+        disabled={true}
+        type="submit"
+      >
+        rename
+      </button>
+      <a
+        href="#"
+        onClick={[Function]}
+      >
+        cancel
+      </a>
+    </footer>
+  </form>
+</Modal>
+`;
+
+exports[`renders 2`] = `
+<Modal
+  ariaHideApp={true}
+  bodyOpenClassName="ReactModal__Body--open"
+  className="modal"
+  closeTimeoutMS={0}
+  contentLabel="branches.rename"
+  isOpen={true}
+  onRequestClose={[Function]}
+  overlayClassName="modal-overlay"
+  parentSelector={[Function]}
+  portalClassName="ReactModalPortal"
+  shouldCloseOnOverlayClick={true}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      branches.rename
+    </h2>
+  </header>
+  <form
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-body"
+    >
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="rename-branch-name"
+        >
+          new_name
+          <em
+            className="mandatory"
+          >
+            *
+          </em>
+        </label>
+        <input
+          autoFocus={true}
+          id="rename-branch-name"
+          maxLength={100}
+          name="name"
+          onChange={[Function]}
+          required={true}
+          size={50}
+          type="text"
+          value="dev"
+        />
+      </div>
+    </div>
+    <footer
+      className="modal-foot"
+    >
+      <button
+        disabled={false}
+        type="submit"
+      >
+        rename
+      </button>
+      <a
+        href="#"
+        onClick={[Function]}
+      >
+        cancel
+      </a>
+    </footer>
+  </form>
+</Modal>
+`;
+
+exports[`renders 3`] = `
+<Modal
+  ariaHideApp={true}
+  bodyOpenClassName="ReactModal__Body--open"
+  className="modal"
+  closeTimeoutMS={0}
+  contentLabel="branches.rename"
+  isOpen={true}
+  onRequestClose={[Function]}
+  overlayClassName="modal-overlay"
+  parentSelector={[Function]}
+  portalClassName="ReactModalPortal"
+  shouldCloseOnOverlayClick={true}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      branches.rename
+    </h2>
+  </header>
+  <form
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-body"
+    >
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="rename-branch-name"
+        >
+          new_name
+          <em
+            className="mandatory"
+          >
+            *
+          </em>
+        </label>
+        <input
+          autoFocus={true}
+          id="rename-branch-name"
+          maxLength={100}
+          name="name"
+          onChange={[Function]}
+          required={true}
+          size={50}
+          type="text"
+          value="dev"
+        />
+      </div>
+    </div>
+    <footer
+      className="modal-foot"
+    >
+      <i
+        className="spinner spacer-right"
+      />
+      <button
+        disabled={true}
+        type="submit"
+      >
+        rename
+      </button>
+      <a
+        href="#"
+        onClick={[Function]}
+      >
+        cancel
+      </a>
+    </footer>
+  </form>
+</Modal>
+`;
index 1db17ec0ffdb0789ac4211bc65a8993459c044a9..cc8717235f3f083584971ed1645b050aeaf2e262 100644 (file)
@@ -79,7 +79,7 @@ export interface Component {
   version?: string;
 }
 
-export interface ComponentConfiguration {
+interface ComponentConfiguration {
   extensions?: ComponentExtension[];
   showBackgroundTasks?: boolean;
   showLinks?: boolean;
index b6577565efdc0934b42acd3ce6a5076fafeadace..1b8f7f606c39e99f7dbc36d93af48da26bdd28e7 100644 (file)
@@ -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>
index 5c4b910f698b8e394dcb32a3b099891786f4340b..f7f3dbf4e865a0583c26e775db5ffeedd47f9d27 100644 (file)
@@ -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 (file)
index 02a8e20..0000000
+++ /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 (file)
index e163481..0000000
+++ /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/DeleteBranchModal.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx
deleted file mode 100644 (file)
index 66d14ed..0000000
+++ /dev/null
@@ -1,103 +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 Modal from 'react-modal';
-import { deleteBranch } from '../../../api/branches';
-import { Branch } from '../../../app/types';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-interface Props {
-  branch: Branch;
-  component: string;
-  onClose: () => void;
-  onDelete: () => void;
-}
-
-interface State {
-  loading: boolean;
-}
-
-export default class DeleteBranchModal extends React.PureComponent<Props, State> {
-  mounted: boolean;
-  state: State = { loading: false };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
-    event.preventDefault();
-    this.setState({ loading: true });
-    deleteBranch(this.props.component, this.props.branch.name).then(
-      () => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-          this.props.onDelete();
-        }
-      },
-      () => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-        }
-      }
-    );
-  };
-
-  handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  render() {
-    const { branch } = this.props;
-    const header = translate('branches.delete');
-
-    return (
-      <Modal
-        isOpen={true}
-        contentLabel={header}
-        className="modal"
-        overlayClassName="modal-overlay"
-        onRequestClose={this.props.onClose}>
-        <header className="modal-head">
-          <h2>{header}</h2>
-        </header>
-        <form onSubmit={this.handleSubmit}>
-          <div className="modal-body">
-            {translateWithParameters('branches.delete.are_you_sure', branch.name)}
-          </div>
-          <footer className="modal-foot">
-            {this.state.loading && <i className="spinner spacer-right" />}
-            <button className="button-red" disabled={this.state.loading} type="submit">
-              {translate('delete')}
-            </button>
-            <a href="#" onClick={this.handleCancelClick}>
-              {translate('cancel')}
-            </a>
-          </footer>
-        </form>
-      </Modal>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx
deleted file mode 100644 (file)
index 181fee7..0000000
+++ /dev/null
@@ -1,129 +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 Modal from 'react-modal';
-import { renameBranch } from '../../../api/branches';
-import { Branch } from '../../../app/types';
-import { translate } from '../../../helpers/l10n';
-
-interface Props {
-  branch: Branch;
-  component: string;
-  onClose: () => void;
-  onRename: () => void;
-}
-
-interface State {
-  loading: boolean;
-  name?: string;
-}
-
-export default class RenameBranchModal extends React.PureComponent<Props, State> {
-  mounted: boolean;
-  state: State = { loading: false };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
-    event.preventDefault();
-    if (!this.state.name) {
-      return;
-    }
-    this.setState({ loading: true });
-    renameBranch(this.props.component, this.state.name).then(
-      () => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-          this.props.onRename();
-        }
-      },
-      () => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-        }
-      }
-    );
-  };
-
-  handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
-    this.setState({ name: event.currentTarget.value });
-  };
-
-  render() {
-    const { branch } = this.props;
-    const header = translate('branches.rename');
-    const submitDisabled =
-      this.state.loading || !this.state.name || this.state.name === branch.name;
-
-    return (
-      <Modal
-        isOpen={true}
-        contentLabel={header}
-        className="modal"
-        overlayClassName="modal-overlay"
-        onRequestClose={this.props.onClose}>
-        <header className="modal-head">
-          <h2>{header}</h2>
-        </header>
-        <form onSubmit={this.handleSubmit}>
-          <div className="modal-body">
-            <div className="modal-field">
-              <label htmlFor="rename-branch-name">
-                {translate('new_name')}
-                <em className="mandatory">*</em>
-              </label>
-              <input
-                autoFocus={true}
-                id="rename-branch-name"
-                maxLength={100}
-                name="name"
-                onChange={this.handleNameChange}
-                required={true}
-                size={50}
-                type="text"
-                value={this.state.name != undefined ? this.state.name : branch.name}
-              />
-            </div>
-          </div>
-          <footer className="modal-foot">
-            {this.state.loading && <i className="spinner spacer-right" />}
-            <button disabled={submitDisabled} type="submit">
-              {translate('rename')}
-            </button>
-            <a href="#" onClick={this.handleCancelClick}>
-              {translate('cancel')}
-            </a>
-          </footer>
-        </form>
-      </Modal>
-    );
-  }
-}
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 (file)
index 4288105..0000000
+++ /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 (file)
index 4edc3ce..0000000
+++ /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__/DeleteBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx
deleted file mode 100644 (file)
index b287058..0000000
+++ /dev/null
@@ -1,98 +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.
- */
-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';
-
-beforeEach(() => {
-  (deleteBranch as jest.Mock<any>).mockClear();
-});
-
-it('renders', () => {
-  const wrapper = shallowRender();
-  expect(wrapper).toMatchSnapshot();
-  wrapper.setState({ loading: true });
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('deletes branch', () => {
-  (deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.resolve());
-  const onDelete = jest.fn();
-  const wrapper = shallowRender(onDelete);
-
-  submitForm(wrapper);
-
-  return doAsync().then(() => {
-    wrapper.update();
-    expect(wrapper.state().loading).toBe(false);
-    expect(onDelete).toBeCalled();
-    expect(deleteBranch).toBeCalledWith('foo', 'feature');
-  });
-});
-
-it('cancels', () => {
-  const onClose = jest.fn();
-  const wrapper = shallowRender(jest.fn(), onClose);
-
-  click(wrapper.find('a'));
-
-  return doAsync().then(() => {
-    expect(onClose).toBeCalled();
-  });
-});
-
-it('stops loading on WS error', () => {
-  (deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.reject(null));
-  const onDelete = jest.fn();
-  const wrapper = shallowRender(onDelete);
-
-  submitForm(wrapper);
-
-  return doAsync().then(() => {
-    wrapper.update();
-    expect(wrapper.state().loading).toBe(false);
-    expect(onDelete).not.toBeCalled();
-    expect(deleteBranch).toBeCalledWith('foo', 'feature');
-  });
-});
-
-function shallowRender(onDelete: () => void = jest.fn(), onClose: () => void = jest.fn()) {
-  const branch: ShortLivingBranch = {
-    isMain: false,
-    name: 'feature',
-    mergeBranch: 'master',
-    type: BranchType.SHORT
-  };
-  const wrapper = shallow(
-    <DeleteBranchModal branch={branch} component="foo" onClose={onClose} onDelete={onDelete} />
-  );
-  (wrapper.instance() as any).mounted = true;
-  return wrapper;
-}
-
-function submitForm(wrapper: ShallowWrapper<any, any>) {
-  submit(wrapper.find('form'));
-  expect(wrapper.state().loading).toBe(true);
-}
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx
deleted file mode 100644 (file)
index 3a1c962..0000000
+++ /dev/null
@@ -1,95 +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.
- */
-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';
-
-beforeEach(() => {
-  (renameBranch as jest.Mock<any>).mockClear();
-});
-
-it('renders', () => {
-  const wrapper = shallowRender();
-  expect(wrapper).toMatchSnapshot();
-  wrapper.setState({ name: 'dev' });
-  expect(wrapper).toMatchSnapshot();
-  wrapper.setState({ loading: true });
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('renames branch', () => {
-  (renameBranch as jest.Mock<any>).mockImplementation(() => Promise.resolve());
-  const onRename = jest.fn();
-  const wrapper = shallowRender(onRename);
-
-  fillAndSubmit(wrapper);
-
-  return doAsync().then(() => {
-    wrapper.update();
-    expect(wrapper.state().loading).toBe(false);
-    expect(onRename).toBeCalled();
-    expect(renameBranch).toBeCalledWith('foo', 'dev');
-  });
-});
-
-it('cancels', () => {
-  const onClose = jest.fn();
-  const wrapper = shallowRender(jest.fn(), onClose);
-
-  click(wrapper.find('a'));
-
-  return doAsync().then(() => {
-    expect(onClose).toBeCalled();
-  });
-});
-
-it('stops loading on WS error', () => {
-  (renameBranch as jest.Mock<any>).mockImplementation(() => Promise.reject(null));
-  const onRename = jest.fn();
-  const wrapper = shallowRender(onRename);
-
-  fillAndSubmit(wrapper);
-
-  return doAsync().then(() => {
-    wrapper.update();
-    expect(wrapper.state().loading).toBe(false);
-    expect(onRename).not.toBeCalled();
-  });
-});
-
-function shallowRender(onRename: () => void = jest.fn(), onClose: () => void = jest.fn()) {
-  const branch: MainBranch = { isMain: true, name: 'master' };
-  const wrapper = shallow(
-    <RenameBranchModal branch={branch} component="foo" onClose={onClose} onRename={onRename} />
-  );
-  (wrapper.instance() as any).mounted = true;
-  return wrapper;
-}
-
-function fillAndSubmit(wrapper: ShallowWrapper<any, any>) {
-  change(wrapper.find('input'), 'dev');
-  submit(wrapper.find('form'));
-  expect(wrapper.state().loading).toBe(true);
-}
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 (file)
index 6f983e3..0000000
+++ /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 (file)
index ea13576..0000000
+++ /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/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap
deleted file mode 100644 (file)
index 934f8ed..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<Modal
-  ariaHideApp={true}
-  bodyOpenClassName="ReactModal__Body--open"
-  className="modal"
-  closeTimeoutMS={0}
-  contentLabel="branches.delete"
-  isOpen={true}
-  onRequestClose={[Function]}
-  overlayClassName="modal-overlay"
-  parentSelector={[Function]}
-  portalClassName="ReactModalPortal"
-  shouldCloseOnOverlayClick={true}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      branches.delete
-    </h2>
-  </header>
-  <form
-    onSubmit={[Function]}
-  >
-    <div
-      className="modal-body"
-    >
-      branches.delete.are_you_sure.feature
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <button
-        className="button-red"
-        disabled={false}
-        type="submit"
-      >
-        delete
-      </button>
-      <a
-        href="#"
-        onClick={[Function]}
-      >
-        cancel
-      </a>
-    </footer>
-  </form>
-</Modal>
-`;
-
-exports[`renders 2`] = `
-<Modal
-  ariaHideApp={true}
-  bodyOpenClassName="ReactModal__Body--open"
-  className="modal"
-  closeTimeoutMS={0}
-  contentLabel="branches.delete"
-  isOpen={true}
-  onRequestClose={[Function]}
-  overlayClassName="modal-overlay"
-  parentSelector={[Function]}
-  portalClassName="ReactModalPortal"
-  shouldCloseOnOverlayClick={true}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      branches.delete
-    </h2>
-  </header>
-  <form
-    onSubmit={[Function]}
-  >
-    <div
-      className="modal-body"
-    >
-      branches.delete.are_you_sure.feature
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <i
-        className="spinner spacer-right"
-      />
-      <button
-        className="button-red"
-        disabled={true}
-        type="submit"
-      >
-        delete
-      </button>
-      <a
-        href="#"
-        onClick={[Function]}
-      >
-        cancel
-      </a>
-    </footer>
-  </form>
-</Modal>
-`;
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/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap
deleted file mode 100644 (file)
index 7867fa4..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<Modal
-  ariaHideApp={true}
-  bodyOpenClassName="ReactModal__Body--open"
-  className="modal"
-  closeTimeoutMS={0}
-  contentLabel="branches.rename"
-  isOpen={true}
-  onRequestClose={[Function]}
-  overlayClassName="modal-overlay"
-  parentSelector={[Function]}
-  portalClassName="ReactModalPortal"
-  shouldCloseOnOverlayClick={true}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      branches.rename
-    </h2>
-  </header>
-  <form
-    onSubmit={[Function]}
-  >
-    <div
-      className="modal-body"
-    >
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="rename-branch-name"
-        >
-          new_name
-          <em
-            className="mandatory"
-          >
-            *
-          </em>
-        </label>
-        <input
-          autoFocus={true}
-          id="rename-branch-name"
-          maxLength={100}
-          name="name"
-          onChange={[Function]}
-          required={true}
-          size={50}
-          type="text"
-          value="master"
-        />
-      </div>
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <button
-        disabled={true}
-        type="submit"
-      >
-        rename
-      </button>
-      <a
-        href="#"
-        onClick={[Function]}
-      >
-        cancel
-      </a>
-    </footer>
-  </form>
-</Modal>
-`;
-
-exports[`renders 2`] = `
-<Modal
-  ariaHideApp={true}
-  bodyOpenClassName="ReactModal__Body--open"
-  className="modal"
-  closeTimeoutMS={0}
-  contentLabel="branches.rename"
-  isOpen={true}
-  onRequestClose={[Function]}
-  overlayClassName="modal-overlay"
-  parentSelector={[Function]}
-  portalClassName="ReactModalPortal"
-  shouldCloseOnOverlayClick={true}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      branches.rename
-    </h2>
-  </header>
-  <form
-    onSubmit={[Function]}
-  >
-    <div
-      className="modal-body"
-    >
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="rename-branch-name"
-        >
-          new_name
-          <em
-            className="mandatory"
-          >
-            *
-          </em>
-        </label>
-        <input
-          autoFocus={true}
-          id="rename-branch-name"
-          maxLength={100}
-          name="name"
-          onChange={[Function]}
-          required={true}
-          size={50}
-          type="text"
-          value="dev"
-        />
-      </div>
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <button
-        disabled={false}
-        type="submit"
-      >
-        rename
-      </button>
-      <a
-        href="#"
-        onClick={[Function]}
-      >
-        cancel
-      </a>
-    </footer>
-  </form>
-</Modal>
-`;
-
-exports[`renders 3`] = `
-<Modal
-  ariaHideApp={true}
-  bodyOpenClassName="ReactModal__Body--open"
-  className="modal"
-  closeTimeoutMS={0}
-  contentLabel="branches.rename"
-  isOpen={true}
-  onRequestClose={[Function]}
-  overlayClassName="modal-overlay"
-  parentSelector={[Function]}
-  portalClassName="ReactModalPortal"
-  shouldCloseOnOverlayClick={true}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      branches.rename
-    </h2>
-  </header>
-  <form
-    onSubmit={[Function]}
-  >
-    <div
-      className="modal-body"
-    >
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="rename-branch-name"
-        >
-          new_name
-          <em
-            className="mandatory"
-          >
-            *
-          </em>
-        </label>
-        <input
-          autoFocus={true}
-          id="rename-branch-name"
-          maxLength={100}
-          name="name"
-          onChange={[Function]}
-          required={true}
-          size={50}
-          type="text"
-          value="dev"
-        />
-      </div>
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <i
-        className="spinner spacer-right"
-      />
-      <button
-        disabled={true}
-        type="submit"
-      >
-        rename
-      </button>
-      <a
-        href="#"
-        onClick={[Function]}
-      >
-        cancel
-      </a>
-    </footer>
-  </form>
-</Modal>
-`;
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 (file)
index 520805e..0000000
+++ /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;
index 9c4d70c270eafac9de3292437001fe90fa798dc1..c32b99adfcb03a13a3ad853aeb488fa5be2a3b12 100644 (file)
@@ -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 });
@@ -162,17 +170,6 @@ export default class CreateProjectForm extends React.PureComponent<Props, State>
                   value={this.state.name}
                 />
               </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')}
@@ -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">
index 0212d094e8e13a52ed26ad68e5e607f791b1b707..bd8c3114b4eaf25d61cbfef548c278a44d9bd30e 100644 (file)
@@ -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();
+});
index 036b78af957c288e4e5cbb022a6fb35a390eea1c..332863ffafee3a78070f3e444fee25854a715715 100644 (file)
@@ -52,23 +52,6 @@ exports[`creates project 1`] = `
           value=""
         />
       </div>
-      <div
-        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"
       >
@@ -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"
@@ -186,23 +180,6 @@ exports[`creates project 2`] = `
           value="name"
         />
       </div>
-      <div
-        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"
       >
@@ -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"
@@ -320,23 +308,6 @@ exports[`creates project 3`] = `
           value="name"
         />
       </div>
-      <div
-        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"
       >
@@ -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>
+`;
index 866830fbb69abcec875437ccfbcdc6adad891aac..dff1d0b74d5a1bda4609ddbd9f8d087af7e12a68 100644 (file)
@@ -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')}
index ea1604dfa5ca51654e692f2e05159f52987b1150..40688087cf02aae2731bc546a4fee4916627bab2 100644 (file)
@@ -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 {
index 1f4ccfc4484c1743e7b64b9c5dce8ae8937f8b1d..b106929fbe47f20614f9ef4a4a9b87b3dfe2e67e 100644 (file)
@@ -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>
 `;
index 68f658bbffa39372cc2e243d6f438cb8a9c1de81..ec8038ea2ecc7a4d912217b9573454c7ec8eb242 100644 (file)
@@ -3178,13 +3178,13 @@ branches.no_support.header.text=Analyze each branch of your project separately w
 branches.delete=Delete Branch
 branches.delete.are_you_sure=Are you sure you want to delete branch "{0}"?
 branches.rename=Rename Branch
-branches.manage=Manage branches
 branches.orphan_branch=Orphan Branch
 branches.orphan_branches=Orphan Branches
 branches.orphan_branches.tooltip=When a target branch of a short-living branch was deleted, this short-living branch becomes orphan.
 branches.main_branch=Main Branch
-branches.settings_hint=To administrate your branches, you have to go to your main branch's {link} tab.
-branches.settings_hint_tab=Administration > Branches
+branches.branch_settings=Branch Settings
+branches.settings_hint=To administrate your project, you have to go to your main branch's {link} tab.
+branches.settings_hint_tab=Administration
 
 
 #------------------------------------------------------------------------------